きなこもち.net

.NET Framework × UiPath,Orchestrator × Azure × AWS × Angularなどの忘備録

Observable × Subject/BehaviorSubject × それぞれの使い方を調べてみた

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

Sample Code

参考

What is the difference between Subject and BehaviorSubject?
AngularのBehaviorSubjectでコンポーネント間の情報を共有する
AngularでAsync pipeを使う