Purpose
Observableについて理解する。
概要
似たような用語(Observer、Observable、Subject、BehaviorSubject、Subjectlikeなど)があり混乱しているため、まずは、そこを整理して、それぞれどのように使い分けていくべきかを調べる。
それぞれの依存関係
いろいろ似たような名前が出てきて混乱したため、それぞれの定義を確認しておく。
class Observable<T> implements Subscribable interface Subscribable<T>
class Subject<T> extends Observable implements SubscriptionLike interface SubscriptionLike extends Unsubscribable interface Unsubscribable
class BehaviorSubject<T> extends Subject
//結局これはよくわからないまま・・・ interface SubjectLike<T> extends Observer, Subscribable
class Observable
利用方法と考え方
Observableは、パブリッシャーとサブスクライバーの間でメッセージを受け渡す機能である。簡単のため、パブリッシャーが通知した値をサブスクライバーで受け取り、ログに出力するというケースで考えをまとめる。
まずは、メッセージを送信するためのパブリッシャーをSubscriber関数として定義する。
ここでは、定期的に変数をインクリメントし、インクリメントした変数をパブリッシュするSubscriberを定義する。nextメソッドに値を引き渡すと、このSubscriber関数を監視しているコンシューマーに値の変更が通知される。
//パブリッシャー/subscriber private countSubscriber(subscriber: Subscriber<number>) { console.log('Subscribe start'); let _count = 0; subscriber.next(_count); setInterval(() => { _count++; subscriber.next(_count); }, 1000); }
次に、コンシューマーを定義する。
//コンシューマー/Observer countObserver: Observer<number> = { next: (x: number) => {console.log('Observer got a next value: ' + x), error: (err: Error) => console.error('Observer got an error: ' + err), complete: () => console.log('Observer got a complete notification') };
最後に、Observable インスタンスを作成する。
ngOnInit() { //インスタンスを作成。コンストラクタの引数に、パブリッシャーを渡す。 this.countObservable == new Observable<number>(this.countSubscriber); //すぐさまSubscribeを始める。subscribeメソッドに、コンシューマーを設定する。 this.countObservable.subscribe(this.countObserver); }
View側で変更を受け取り、画面に直接変更を反映させるためには、asyncパイプを利用する。この場合、上記のSubscribeメソッドの設定と、コンシューマーの設定は不要となる。
async パイプ を 利用 する と、 ビュー( テンプレート) 側 で Observable/ Promise オブジェクト を 受け取り、 それら が 値 を 返し た タイミング で、 その 値 を 取り出す こと が でき ます。 山田 祥寛. Angularアプリケーションプログラミング (Kindle の位置No.5088-5089). 株式会社技術評論社. Kindle 版.
<!--複数のメンバーを持ったオブジェクトに対する処理の例--> <div>{{ ( countObservable| async)}}</div> <!--複数のメンバーを持ったオブジェクトに対する処理の例--> <div>{{ ( httpbingetObservable| async)?.origin }}</div>
Point
Observableでは、インスタンスの作成時にのみSubscriberに値の変更ロジックを設定することができる。画面の初期表示でバックエンドにリクエストを送信し、情報を取得するなどの処理において、非同期にする場合はこれで問題なさそう。一方で、ユーザーの操作を起点として情報に変更が発生し、その変更をコンシューマーに届けたいようなシナリオの場合実現が難しい。そういう場合は、Subject、BehabiorSubjectを利用する。
Subject, BehaviorSubject
class Subject
Subjectクラスは、Observableクラスの派生クラスである。Observableクラスのインスタンスにsubscriber関数、Observer関数を定義することなくパブリッシャーとコンシューマーの機能を実現することができる。
//①Subjectクラスのインスタンスを生成 const subject = new Subject(); //②コンシューマーの設定 subject.subscribe(x => console.log(x)); //③Subscriber関数を定義せず、直接Nextメソッドに値を設定することができる。 subject.next(1);
ただし、②と③の順番が逆になった場合、Consoleには何も出力されない。これは、nextメソッドで渡された値(1)が③のSubscribe処理の前に破棄されるためである。
class BehaviorSubject
BehaviorSubjectクラスは、Subjectクラスの派生クラスで、Subjectクラスと同様の利用ができる。このクラスでは、現在の値を保持することができ、その値を任意のタイミングで取得することができる。
また、コンストラクタに設定することで、初期値を持つことができる。
const subject = new BehaviorSubject(0); subject.subscribe(x => console.log(x)); subject.next(1);
Subjectクラスとは違い、値を保持するため、以下のように実行順序を変えても期待通りコンソールには1が表示される。
const subject = new BehaviorSubject(0); subject.next(1); subject.subscribe(x => console.log(x));
Point
Subject, SubjectBehabiorクラスでは、nextメソッドを任意の場所で呼び出してコンシューマーに向けてメッセージを送信することができる。そのため、Observableで実現が難しかったユーザー操作を起点とした処理で利用することができる。
Sample
参考
What is the difference between Subject and BehaviorSubject?
AngularのBehaviorSubjectでコンポーネント間の情報を共有する
AngularでAsync pipeを使う