RxJS SubjectまたはObservableの現在の値を取得するにはどうすればよいですか?


207

Angular 2サービスがあります。

import {Storage} from './storage';
import {Injectable} from 'angular2/core';
import {Subject}    from 'rxjs/Subject';

@Injectable()
export class SessionStorage extends Storage {
  private _isLoggedInSource = new Subject<boolean>();
  isLoggedIn = this._isLoggedInSource.asObservable();
  constructor() {
    super('session');
  }
  setIsLoggedIn(value: boolean) {
    this.setItem('_isLoggedIn', value, () => {
      this._isLoggedInSource.next(value);
    });
  }
}

すべてがうまくいきます。しかし、サブスクライブする必要のない別のコンポーネントがあります。特定の時点でisLoggedInの現在の値を取得する必要があるだけです。これどうやってするの?

回答:


341

A SubjectまたはObservable現在の値を持っていません。値が発行されると、サブスクライバーに渡され、それが使用されますObservable

現在の値が必要な場合BehaviorSubjectは、まさにその目的のために設計されたものを使用してください。BehaviorSubject最後に発行された値を保持し、すぐに新しいサブスクライバーに発行します。

また、メソッドがあります getValue()現在の値を取得ます。


1
こんにちは、私の悪いです。rxjs5でも同じです。上記のソースコードリンクはrxjs4を参照していました。
シュレディンガーのコード2017

84
RxJS 5の作者からの重要な注意:使用getValue()は、何か間違ったことをしているという重大な警告です。脱出ハッチとしてあります。一般に、RxJSで行うことはすべて宣言型である必要があります。getValue()必須です。を使用している場合はgetValue()、99.9%の確率で何か間違ったことや奇妙なことをしています。
Ben Lesh 2017

1
@BenLesh私が持っていて、BehaviorSubject<boolean>(false)それをトグルしたい場合はどうなりますか?
ボブ2017年

3
@BenLesh getValue()は、たとえばonClick-> dispatchToServer(x.getValue())などの瞬時のアクションを実行する場合に非常に役立ちますが、監視可能なチェーンでは使用しません。
FlavorScape 2018

2
@IngoBürk私があなたの提案を正当化できる唯一の方法foo$は、それがaであると知らなかった場合ですBehaviorSubject(つまり、それはObservable遅延ソースとして定義されており、多分ホットまたはコールドです)。ただし、それ自体で使用.next$foo続けるので、実装はそれ a であること依存しているBehaviorSubjectので.value、最初の場所で現在の値を取得するためだけに使用するだけの理由はありません。
Simon_Weaver

145

あなたがすべき唯一の方法 観察可能/件名「外」の値を取得するには、購読しています!

使用しているgetValue()場合は、宣言的パラダイムで不可欠なことをしています。それは脱出ハッチとして存在しますが、使用するべきではない時間の99.9%getValue()いくつか興味深い点がありますgetValue()でしょうが:被写体が解除されている場合は、エラーがスローされます、それはエラー状態ですので、被写体が死んでいる場合、それは価値を得ることからあなたを防ぐことができます、などしかしが、再び、それはエスケープとしてありますまれな状況のためのハッチ。

サブジェクトまたは監視可能オブジェクトから「Rx-y」の方法で最新の値を取得するには、いくつかの方法があります。

  1. 使用BehaviorSubject:しかし、実際にそれをサブスクライブします。初めて購読するときBehaviorSubjectと、受信または初期化された前の値が同期的に送信されます。
  2. ReplaySubject(N):を使用すると、N値がキャッシュされ、新しいサブスクライバーに再生されます。
  3. A.withLatestFrom(B):この演算子を使用して、オブザーバブルがB発生したときにオブザーバブルから最新の値を取得しAます。両方の値を配列で提供します[a, b]
  4. A.combineLatest(B):この演算子を使用して、またはからA、または発行するBたびに、最新の値を取得します。配列で両方の値を提供します。AB
  5. shareReplay():を通じて監視可能なマルチキャストを作成しReplaySubjectますが、エラー時に監視可能を再試行できます。(基本的には、promise-yキャッシュ動作を提供します)。
  6. publishReplay()publishBehavior(initialValue)multicast(subject: BehaviorSubject | ReplaySubject)、など:その他の演算子レバレッジBehaviorSubjectReplaySubject。同じことの異なるフレーバー、それらは基本的に、サブジェクトを通してすべての通知を集めることにより、オブザーバブルをソースにマルチキャストします。あなたconnect()は件名でソースを購読するために呼び出す必要があります。

19
なぜgetValue()の使用が危険なのかについて詳しく説明できますか?ユーザーがボタンをクリックした瞬間にオブザーバブルの現在の値が1回だけ必要な場合はどうなりますか?値を購読してすぐに購読を解除する必要がありますか?
Flame

5
時々大丈夫です。しかし、多くの場合、コードの作成者が、本来あるべきではない場所で(通常は副作用を伴う)非常に命令的なことをしていることを示しています。あなたもclick$.mergeMap(() => behaviorSubject.take(1))あなたの問題を解決するために行うことができます。
Ben Lesh 2017

1
素晴らしい説明!実際、Observableを維持してメソッドを使用できる場合は、はるかに優れた方法です。BehaviourSubjectとして定義されている少数の1つだけがObservableで使用されるtypescriptのコンテキストでは、一貫性の低いコードになります。あなたが提案した方法により、私はどこでもObservable型を維持することができました。
JLavoie 2017年

2
現在の値を単にディスパッチしたい場合(クリック時にサーバーに送信する場合など)は、getValue()を実行すると非常に便利です。ただし、監視可能な演算子をチェーンする場合は使用しないでください。
FlavorScape

1
私はAuthenticationService使用するBehaviourSubject電流を格納するためには、状態(ログインboolean true又はfalse)。これはisLoggedIn$、状態がいつ変化したかを知りたいサブスクライバーのオブザーバブルを公開します。また、get isLoggedIn()プロパティを公開します。これは、getValue()基礎を呼び出すことによって現在のログイン状態を返します。BehaviourSubjectこれは、現在の状態を確認するために私の認証ガードによって使用されます。これはgetValue()私には賢明な使い方のようです...?
Dan King

13

同様の状況で、Subjectのサブスクライバーがその値が到着した後にサブジェクトをサブスクライブしました。

この場合、BehaviorSubjectに似たReplaySubjectがチャームのように機能することがわかりました。そして、より良い説明へのリンクがあります:http : //reactivex.io/rxjs/manual/overview.html#replaysubject


1
Angular4アプリで役立ちました-サブスクリプションをコンポーネントコンストラクターからngOnInit()に移動する必要がありました(そのようなコンポーネントはルート間で共有されます)。誰かが同様の問題を抱えている場合に備えて、ここを離れます
Luca

1
サービスを使用してAPIから値を取得し、さまざまなコンポーネントの変数を設定するAngular 5アプリに問題がありました。サブジェクト/オブザーバブルを使用していましたが、ルート変更後に値がプッシュされませんでした。ReplaySubjectは、Subjectの代わりのドロップであり、すべてを解決しました。
jafaircl 2017年

1
必ずしているが、ReplaySubject(1)を使用して、そうでない場合は、新規加入者が順番にすべての以前に放出された値を取得します-この実行時に常に明白ではありません
Drenai

@Drenaiは、ReplaySubject(1)がBehaviorSubject()と同じように動作することを理解している限り
Kfir Erez

まったく同じではありません。大きな違いは、BehaviourSubjectがnext()まだ呼び出されていないのに、ReplaySubjectがサブスクライブされている場合、サブスクライブ時にデフォルト値がすぐに出力されないことです。たとえば、サービスで
監視

5
const observable = of('response')

function hasValue(value: any) {
  return value !== null && value !== undefined;
}

function getValue<T>(observable: Observable<T>): Promise<T> {
  return observable
    .pipe(
      filter(hasValue),
      first()
    )
    .toPromise();
}

const result = await getValue(observable)
// Do the logic with the result
// .................
// .................
// .................

それを実装する方法については、ここから記事全体を確認できます。 https://www.imkrish.com/how-to-get-current-value-of-observable-in-a-clean-way/


3

子コンポーネントで同じ問題が発生しましたが、最初はサブジェクトの現在の値を取得してから、サブジェクトをサブスクライブして変更をリッスンする必要があります。サービスの現在の値を維持するだけで、コンポーネントがアクセスできるようにします。例:

import {Storage} from './storage';
import {Injectable} from 'angular2/core';
import {Subject}    from 'rxjs/Subject';

@Injectable()
export class SessionStorage extends Storage {

  isLoggedIn: boolean;

  private _isLoggedInSource = new Subject<boolean>();
  isLoggedIn = this._isLoggedInSource.asObservable();
  constructor() {
    super('session');
    this.currIsLoggedIn = false;
  }
  setIsLoggedIn(value: boolean) {
    this.setItem('_isLoggedIn', value, () => {
      this._isLoggedInSource.next(value);
    });
    this.isLoggedIn = value;
  }
}

現在の値を必要とするコンポーネントは、サービスからそれにアクセスすることができます。つまり、

sessionStorage.isLoggedIn

これが正しい方法かどうかはわかりません:)


2
コンポーネントビューでオブザーバブルの値が必要な場合は、asyncパイプを使用できます。
IngoBürk2017年

2

同様に見える回答が反対票を投じられました。しかし、私がここで提案していることは、限られたケースで正当化できると思います。


オブザーバブルに現在の値がないことは事実ですが、多くの場合、すぐに利用可能な値を持っています。たとえば、redux / flux / akitaストアでは、いくつかのオブザーバブルに基づいて中央ストアからデータをリクエストでき、その値は通常すぐに利用できます。

この場合は、を実行するsubscribeと、値がすぐに返されます。

それでは、あなたがサービスへの呼び出しがあったとしましょう、と終了時に、あなたはあなたの店から何かの最新の値を取得したい潜在的に放出しない可能性があること

あなたはこれを試みるかもしれません(そしてあなたは可能な限り「パイプの内側」に物事を保つべきです):

 serviceCallResponse$.pipe(withLatestFrom(store$.select(x => x.customer)))
                     .subscribe(([ serviceCallResponse, customer] => {

                        // we have serviceCallResponse and customer 
                     });

これの問題は、セカンダリオブザーバブルが値を放出するまでブロックされることです。

私は最近、値がすぐに利用できる場合にのみオブザーバブルを評価する必要があることに気付きました。さらに重要なのは、値が利用できない場合にそれを検出できるようにする必要があったことです。私はこれをやった:

 serviceCallResponse$.pipe()
                     .subscribe(serviceCallResponse => {

                        // immediately try to subscribe to get the 'available' value
                        // note: immediately unsubscribe afterward to 'cancel' if needed
                        let customer = undefined;

                        // whatever the secondary observable is
                        const secondary$ = store$.select(x => x.customer);

                        // subscribe to it, and assign to closure scope
                        sub = secondary$.pipe(take(1)).subscribe(_customer => customer = _customer);
                        sub.unsubscribe();

                        // if there's a delay or customer isn't available the value won't have been set before we get here
                        if (customer === undefined) 
                        {
                           // handle, or ignore as needed
                           return throwError('Customer was not immediately available');
                        }
                     });

上記のすべてについてsubscribe、値を取得するために使用していることに注意してください(@Benが説明しているように)。.value私が持っていても、プロパティを使用していませんBehaviorSubject


1
ところで、デフォルトでは「スケジュール」は現在のスレッドを使用するため、これは機能します。
Simon_Weaver

1

やり過ぎに聞こえるかもしれませんが、これはObservableタイプを維持し、ボイラープレートを減らすためのもう1つの「可能な」ソリューションです...

Observableの現在の値を取得するために、常に拡張ゲッターを作成できます。

これを行うにObservable<T>は、global.d.ts型宣言ファイルでインターフェースを拡張する必要があります。次に、拡張ゲッターobservable.extension.tsファイルに実装し、最後にタイピングと拡張ファイルの両方をアプリケーションに含めます。

このStackOverflow Answerを参照して、Angularアプリケーションに拡張機能を含める方法を知ることができます。

// global.d.ts
declare module 'rxjs' {
  interface Observable<T> {
    /**
     * _Extension Method_ - Returns current value of an Observable.
     * Value is retrieved using _first()_ operator to avoid the need to unsubscribe.
     */
    value: Observable<T>;
  }
}

// observable.extension.ts
Object.defineProperty(Observable.prototype, 'value', {
  get <T>(this: Observable<T>): Observable<T> {
    return this.pipe(
      filter(value => value !== null && value !== undefined),
      first());
  },
});

// using the extension getter example
this.myObservable$.value
  .subscribe(value => {
    // whatever code you need...
  });

0

最後に出力された値をObservableとは別に保存できます。その後、必要に応じて読んでください。

let lastValue: number;

const subscription = new Service().start();
subscription
    .subscribe((data) => {
        lastValue = data;
    }
);

1
オブザーバブルの外にいくつかのものを保存することは、反応的なアプローチではありません。代わりに、観測可能なストリーム内を流れるデータをできるだけ多く持つ必要があります。
ganqqwerty 2018

0

これを行う最善の方法は、を使用Behaviur Subjectすることです。以下に例を示します。

var sub = new rxjs.BehaviorSubject([0, 1])
sub.next([2, 3])
setTimeout(() => {sub.next([4, 5])}, 1500)
sub.subscribe(a => console.log(a)) //2, 3 (current value) -> wait 2 sec -> 4, 5

0

サブスクリプションを作成し、最初に発生したアイテムを破棄した後に破棄できます。Pipeは、Observableを入力として使用し、別のObservableを出力として返しますが、最初のObservableは変更しません。Angular 8.1.0。パッケージ:"rxjs": "6.5.3""rxjs-observable": "0.0.7"

  ngOnInit() {

    ...

    // If loading with previously saved value
    if (this.controlValue) {

      // Take says once you have 1, then close the subscription
      this.selectList.pipe(take(1)).subscribe(x => {
        let opt = x.find(y => y.value === this.controlValue);
        this.updateValue(opt);
      });

    }
  }
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.