73. constructor注入と BehaviorSubject — 旧世代のサービスの読み方
最後はサービスまわりの旧作法を2つまとめて読み解きます。1つ目は注入の書き方です。inject() 関数が主流になる前は、constructor(private http: HttpClient) {} という「コンストラクタ注入」が標準でした。引数に private などの修飾子を付けると、TypeScriptがプロパティ宣言と代入を自動でやってくれる省略記法で、this.http として使えるようになります。読み方はシンプルで、コンストラクタの引数リストがそのクラスの依存一覧です。
2つ目は状態共有の旧定番、BehaviorSubject です。「現在値を1つ覚えていて、購読すると即座にその値をくれる Subject」で、private な BehaviorSubject を持ち、外へは asObservable() で読み取り専用にして公開し、変更はメソッド経由で .next() する — これが「サービスのシグナルでストアを作る」パターンのRxJS版です。対応関係はきれいで、.next() が set()、asObservable() が asReadonly()、combineLatest + map で作る派生値が computed、テンプレートの asyncパイプがシグナルの () 呼び出しにあたります。
既存の BehaviorSubject ストアを見つけても、慌てて書き換える必要はありません。動いているコードはそのままでよく、新しく書く部分だけシグナルにし、境界は toSignal() で橋渡しするのが安全な進め方です。読むときのコツは「.next() を呼んでいる場所を探す」こと。それが状態の書き込み経路のすべてなので、シグナルの set() を探すのと同じ要領でデータの流れを追えます。
サンプルコード(フレームワーク環境が必要なため表示のみ)
import { Injectable } from '@angular/core';
import { BehaviorSubject, map } from 'rxjs';
// 旧世代の定番ストア(シグナル版ストアのRxJS版)
@Injectable({ providedIn: 'root' })
export class CartService {
// 現在値を持つ Subject。private で書き込みを閉じ込める
private itemsSubject = new BehaviorSubject<string[]>([]);
// 外へは読み取り専用で公開(= asReadonly() に相当)
items$ = this.itemsSubject.asObservable();
// 派生値(= computed に相当)
count$ = this.items$.pipe(map((items) => items.length));
// 書き込みはメソッド経由の .next() だけ(= set() に相当)
add(item: string) {
this.itemsSubject.next([...this.itemsSubject.value, item]);
}
}
// 利用側テンプレート: {{ (count$ | async) ?? 0 }} 件
学んだ概念を、素朴な例から実務寄りの例まで5つの具体例で確認します。
公式ドキュメントで詳しく ↗