Subjectとは
Subjectは、RxJSにおいて特殊な種類のObservableです。通常のObservableが単方向のデータフローを提供するのに対し、Subjectは「Observable」と「Observer」の両方の性質を持つハイブリッドな存在です。
Subjectは以下の特徴を持ちます。
- データを発行できる(Observer機能)
- データを購読できる(Observable機能)
- 複数の購読者に同じ値を届けられる(マルチキャスト)
- 購読後に発生した値のみを受け取る(Hot Observable的性質)
Subjectの基本的な使い方
import { Subject } from 'rxjs';
// Subject(主体)を作成
const subject = new Subject<number>();
// Observerとして購読
subject.subscribe(value => console.log('Observer A:', value));
subject.subscribe(value => console.log('Observer B:', value));
// Observableとして値を発行
subject.next(1); // 両方の購読者に値を発行
subject.next(2); // 両方の購読者に値を発行
// 新たな購読者を追加(遅延購読)
subject.subscribe(value => console.log('Observer C:', value));
subject.next(3); // 全ての購読者に値を発行
// 完了を通知
subject.complete();
実行結果
Observer A: 1
Observer B: 1
Observer A: 2
Observer B: 2
Observer A: 3
Observer B: 3
Observer C: 3
通常のObservableとの違い
Subjectは Hot Observable であり、通常のCold Observableとは以下の点で異なります。
- 購読の有無にかかわらずデータが発行される
- 複数の購読者に同じ値を共有できる(マルチキャスト)
.next()
で外部から値を発行できる- 過去の値は保持されず、購読後の値のみを受け取る
Subjectとマルチキャスティング
Subjectの重要な機能の一つが「マルチキャスティング」です。
これは一つのデータソースを複数の購読者に効率的に配信する機能です。
import { Subject, interval } from 'rxjs';
import { take } from 'rxjs/operators';
// データソース
const source$ = interval(1000).pipe(take(3));
// マルチキャスト用のSubject
const subject = new Subject<number>();
// ソースをSubjectに接続
source$.subscribe(subject); // Subjectが購読者として機能
// 複数の観測者がSubjectを購読
subject.subscribe(value => console.log('Observer 1:', value));
subject.subscribe(value => console.log('Observer 2:', value));
実行結果
Observer 1: 0
Observer 2: 0
Observer 1: 1
Observer 2: 1
Observer 1: 2
Observer 2: 2
このパターンはシングルソース・マルチキャストとも呼ばれ、一つのデータソースを複数の購読者に効率的に配信する際に使用されます。
Subjectの2つの使い方
Subjectには主に2つの使い方があります。それぞれ、用途やふるまいが異なります。
1. 自分で .next()
を呼ぶパターン
Subjectが データ発行の主体(Observable) として使われます。
このパターンはイベント通知や状態更新のような「明示的な値の送信」に適しています。
const subject = new Subject<string>();
subject.subscribe(val => console.log('Observer A:', val));
subject.next('Hello');
subject.next('World');
Observer A: Hello
Observer A: World
2. Observableを中継するパターン(マルチキャスト)
Subjectが ObserverとしてObservableから値を受け取り、中継する 役割を果たします。
この使い方は、Cold ObservableをHotに変換してマルチキャスト するのに便利です。
const source$ = interval(1000).pipe(take(3));
const subject = new Subject<number>();
// Observable → Subject(中継)
source$.subscribe(subject);
// Subject → 複数購読者へ配信
subject.subscribe(val => console.log('Observer 1:', val));
subject.subscribe(val => console.log('Observer 2:', val));
Observer 1: 0
Observer 2: 0
Observer 1: 1
Observer 2: 1
Observer 1: 2
Observer 2: 2
TIP
.next()
を自分で呼ぶ場合は「自ら話す人」、Observableから受け取って中継する場合は「他人の話をマイクで拡声する人」のようにイメージすると理解しやすくなります。
Subjectの実践的なユースケース
Subjectは以下のようなシナリオで特に有用です。
- ステート管理 - アプリケーションの状態を共有・更新
- イベントバス - コンポーネント間の通信
- HTTP応答の共有 - 同一APIコールの結果を複数コンポーネントで共有
- UIイベントの集中管理 - 様々なUI操作を一か所で処理
例: イベントバスの実装
import { Subject } from 'rxjs';
import { filter } from 'rxjs/operators';
interface AppEvent {
type: string;
payload: any;
}
// アプリケーション全体のイベントバス
const eventBus = new Subject<AppEvent>();
// 特定のイベントタイプを購読
eventBus.pipe(
filter(event => event.type === 'USER_LOGGED_IN')
).subscribe(event => {
console.log('ユーザーログイン:', event.payload);
});
// 別のイベントタイプを購読
eventBus.pipe(
filter(event => event.type === 'DATA_UPDATED')
).subscribe(event => {
console.log('データ更新:', event.payload);
});
// イベント発行
eventBus.next({ type: 'USER_LOGGED_IN', payload: { userId: '123', username: 'test_user' } });
eventBus.next({ type: 'DATA_UPDATED', payload: { items: [1, 2, 3] } });
まとめ
Subjectは、RxJSエコシステムにおいて以下の役割を果たす重要な構成要素です。
- Observer(観測者)とObservable(被観測者)の両方の特性を持つ
- コールドなObservableをホットに変換する手段を提供
- 複数の購読者に同じデータストリームを効率的に配信
- コンポーネント間やサービス間の通信を容易にする
- 状態管理やイベント処理のための基盤を提供
Subject系には、この基本Subject以外にも、BehaviorSubject、ReplaySubject、AsyncSubjectなど特化型のSubjectも存在します。それらについてはSubjectの種類で詳しく解説します。