web-dev-qa-db-ja.com

サブスクライブせずにObservableから現在の値を取得します(一度だけ値が必要です)

タイトルは本当にそれをすべて言っています。購読せずにObservableから現在の値を取得するにはどうすればよいですか?現在の値が一度だけ欲しいのですが、新しい値が入ってくるのではなく...

28
EricC

BehaviorSubject を使用する必要があります。

  • BehaviorSubjectはReplaySubjectに似ていますが、最後のパブリケーションのみを記憶する点が異なります。
  • BehaviorSubjectでは、デフォルト値Tを指定する必要もあります。これは、すべてのサブスクライバーが値をすぐに受け取ることを意味します(既に完了していない場合)。

Observableによって公開された最新の値を提供します。

BehaviorSubjectは、getterという名前のvalueプロパティを提供して、渡された最新の値を取得します。


PLUNKER DEMO

  • この例では、値「a」がコンソールに書き込まれます。
  //Declare a Subject, you'll need to provide a default value.
  var subject: BehaviorSubject<string> = new BehaviorSubject("a");

使用法:

console.log(subject.value); // will print the current value
31
Ankit Singh

クイックアンサー:

...新しい値ではなく、現在の値が1回だけ欲しい.

引き続きsubscribeを使用しますが、pipe(take(1))を使用するため、単一の値が得られます。

例えば。 myObs$.pipe(take(1)).subscribe(value => alert(value));

参照: first()take(1)またはsingle() の比較


長い答え:

一般的なルールは、subscribe()でオブザーバブルからのみ値を取得することです。

(またはAngularを使用している場合は非同期パイプ)

BehaviorSubjectには間違いなくその場所があり、RxJSを使い始めたときは、値を取得するためにbs.value()をよく使用していました。 RxJSストリームがアプリケーション全体に広がると(そしてそれがあなたの望むことです!)、これを行うのはますます難しくなります。多くの場合、実際には.asObservable()が基本型を「隠す」ために使用されますprevent誰かが.value()を使用しないようにします。時間の経過とともになぜそれが行われるのかを理解し始めるでしょう。さらに、遅かれ早かれis n't a BehaviorSubjectの値が必要になりますが、それを実現する方法はありません。

ただし、元の質問に戻ります。特に、BehaviorSubjectを使用して「チート」したくない場合。

より良いアプローチは、常にsubscribeを使用して値を取得することです。

obs$.pipe(take(1)).subscribe(value => { ....... })

OR

obs$.pipe(first()).subscribe(value => { ....... })

これら2つのfirst()の違いは、ストリームが既に完了している場合はエラーになり、ストリームが完了した場合はtake(1)はオブザーバブルを出力しません。すぐに利用できる値はありません。

注:これは、BehaviorSubjectを使用している場合でも、より適切なプラクティスと見なされます。

ただし、上記のコードを試すと、サブスクライブ関数のクロージャ内でオブザーバブルの「値」が「スタック」し、現在のスコープで必要になる可能性があります。本当に必要な場合、これを回避する方法の1つは次のとおりです。

const obsValue = undefined;
const sub = obs$.pipe(take(1)).subscribe(value => obsValue = value);
sub.unsubscribe();

// we will only have a value here if it was IMMEDIATELY available
alert(obsValue);

上記のサブスクライブ呼び出しは、値に対してwaitではないことに注意してください。すぐに利用できるものがない場合、サブスクライブ関数は呼び出されません。「後で表示される」のを防ぐために、そこに意図的にサブスクライブ解除呼び出しを配置し​​ます。

そのため、これは非常に不格好に見えるだけでなく、http呼び出しの結果値など、すぐに利用できないものでは動作しませんが、実際には動作サブジェクト(またはもっと重要なのは*であるもの)で動作しますアップストリームで、BehaviorSubject **、または2つのcombineLatest値をとるBehaviorSubjectであることがわかっています)。絶対に(obs$ as BehaviorSubject)-しないでください!

この前の例はまだ一般的に悪い習慣と見なされています-それは混乱です。値がすぐに使用可能かどうかを確認し、そうでない場合は検出できるようにする場合にのみ、以前のコードスタイルを実行します。

最善のアプローチ

すべてを可能な限り観察可能な状態に保ち、値が絶対に必要な場合にのみサブスクライブし、私がやっていることを含むスコープに値を「抽出」しようとしないなら、あなたははるかに良いです上記。

例えば。あなたの動物園が開いている場合、動物のレポートを作成したいとしましょう。次のようなzooOpen$の「抽出された」値が必要だと思うかもしれません。

間違った方法

zooOpen$: Observable<boolean> = of(true);    // is the Zoo open today?
bear$: Observable<string> = of('Beary');
lion$: Observable<string> = of('Liony');

runZooReport() {

   // we want to know if Zoo is open!
   // this uses the approach described above

   const zooOpen: boolean = undefined;
   const sub = this.zooOpen$.subscribe(open => zooOpen = open);
   sub.unsubscribe();

   // 'zooOpen' is just a regular boolean now
   if (zooOpen) 
   {
      // now take the animals, combine them and subscribe to it
      combineLatest(this.bear$, this.lion$).subscribe(([bear, lion]) => {

          alert('Welcome to the Zoo! Today we have a bear called ' + bear + ' and a lion called ' + lion); 
      });
   }
   else 
   {
      alert('Sorry Zoo is closed today!');
   }
}

なぜこれはSO BAD

  • zooOpen$がWebサービスから来た場合はどうなりますか?前の例はどのように機能しますか?実際、サーバーがどれほど高速であっても問題はありません。zooOpen$がHTTPオブザーバブルの場合、上記のコードで値を取得することはありません。
  • このレポートをこの関数の「外部」で使用する場合はどうなりますか。これで、alertがこのメソッドにロックされました。他の場所でレポートを使用する必要がある場合は、これを複製する必要があります!

良い方法

関数の値にアクセスしようとする代わりに、代わりに新しいObservableを作成し、サブスクライブさえしない関数を検討してください!

代わりに、「外部」で消費できる新しいオブザーバブルを返します。

すべてをオブザーバブルとして保持し、switchMapを使用して決定を下すことにより、それ自体が他のオブザーバブルのソースとなり得る新しいオブザーバブルを作成できます。

getZooReport() {

  return this.zooOpen$.pipe(switchMap(zooOpen => {

     if (zooOpen) {

         return combineLatest(this.bear$, this.lion$).pipe(map(([bear, lion] => {

                 // this is inside 'map' so return a regular string
                 return "Welcome to the Zoo! Today we have a bear called ' + bear + ' and a lion called ' + lion;
              }
          );
      }
      else {

         // this is inside 'switchMap' so *must* return an observable
         return of('Sorry the Zoo is closed today!');
      }

   });
 }

上記のは新しいobservableを作成するので、それを他の場所で実行し、必要に応じてさらにパイプすることができます。

 const zooReport$ = this.getZooReport();
 zooReport$.pipe(take(1)).subscribe(report => {
    alert('Todays report: ' + report);
 });

 // or take it and put it into a new pipe
 const zooReportUpperCase$ = zooReport$.pipe(map(report => report.toUpperCase()));

次のことに注意してください。

  • 私は絶対に必要になるまで購読しません-この場合、それは関数の外にあります
  • 「駆動」オブザーバブルはzooOpen$で、switchMapを使用して、最終的にgetZooReport()から返される別のオブザーバブルに「切り替え」ます。
  • zooOpen$が変更された場合の動作は、すべてをキャンセルし、最初のswitchMap内で再び開始します。詳細については、switchMapを参照してください。
  • 注:switchMap内のコードは、新しいオブザーバブルを返す必要があります。 of('hello')を使用してすばやく作成するか、combineLatestなどの別のオブザーバブルを返すことができます。
  • 同様に:mapは通常の文字列を返す必要があります。

私がhadになるまでサブスクライブしないというメンタルノートを書き始めるとすぐに、突然、より生産的で、柔軟性があり、よりクリーンで保守可能なコードを書き始めました。

別の最後の注意:このアプローチをAngularとともに使用する場合、| asyncパイプを使用することにより、単一のsubscribeなしで上記のZooレポートを作成できます。これは素晴らしい例です。 「あなたが持っているまでサブスクライブしない」の原則は、実際にはプリンシパルです。

// in your angular .ts file for a component
const zooReport$ = this.getZooReport();

そしてあなたのテンプレートで:

<pre> {{ zooReport$ | async }} </pre>

こちらの私の回答もご覧ください:

https://stackoverflow.com/a/54209999/1694

また、混乱を避けるために上記には触れていません。

  • tap() は、「オブザーバブルから値を取得する」のに役立つ場合があります。あなたがその演算子に精通していないなら、それを読んでください。 RxJSは「パイプ」を使用し、tap()演算子は「パイプにタップ」してそこにあるものを確認する方法です。
7
Simon_Weaver

Observableコンストラクターを使用して、任意のタイプの監視可能なストリームを作成します。コンストラクターは、observableのsubscribe()メソッドの実行時に実行するサブスクライバー関数を引数として受け取ります。サブスクライバー関数はObserverオブジェクトを受け取り、値をオブザーバーのnext()メソッドに公開できます。

@Component({
  selector: 'async-observable-pipe',
  template: '<div><code>observable|async</code>: Time: {{ time | async }} . 
</div>'
})
export class AsyncObservablePipeComponent {
  time = new Observable<string>((observer: Observer<string>) => {
    setInterval(() => observer.next(new Date().toString()), 1000);
  });
}
0
Kshitij Tiwari

これがあなたが探しているものかどうかわかりません。おそらくその動作サブジェクトをサービスに記述してください。プライベートとして宣言し、設定した値のみを公開します。このようなもの

 @Injectable({
   providedIn: 'root'
 })
  export class ConfigService {
    constructor(private bname:BehaviorSubject<String>){
       this.bname = new BehaviorSubject<String>("currentvalue");
    }

    getAsObservable(){
       this.bname.asObservable();
    }
 }

これにより、外部ユーザーはbehaviorSubjectにサブスクライブするアクセス権のみを持ち、サービスに目的の値を設定できます。

0
Joey587