web-dev-qa-db-de.com

Aktuellen Wert von Observable abrufen, ohne zu abonnieren

Der Titel sagt wirklich alles. Wie kann ich den aktuellen Wert von einem Observable erhalten, ohne ihn zu abonnieren? Ich möchte nur den aktuellen Wert einmal und keine neuen Werte, wie sie kommen ...

18
EricC

Sie müssen BehaviorSubject verwenden.

  • BehaviorSubject ähnelt ReplaySubject, nur dass es sich nur an die letzte Veröffentlichung erinnert. 
  • Bei BehaviorSubject müssen Sie auch einen Standardwert von T angeben. Dies bedeutet, dass alle Abonnenten sofort einen Wert erhalten (sofern nicht bereits abgeschlossen).

Sie erhalten den neuesten von Observable veröffentlichten Wert.

BehaviorSubject stellt eine getter-Eigenschaft mit dem Namen value bereit, um den neuesten Wert abzurufen.


PLUNKER DEMO

  • In diesem Beispiel wird der Wert 'a' in die Konsole geschrieben:

  //Declare a Subject, you'll need to provide a default value.
  var subject: BehaviorSubject<string> = new BehaviorSubject("a");

VERWENDUNGSZWECK: 

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

Schnelle Antwort:

... Ich möchte nur einmal den aktuellen Wert und keine neuen Werte, wie sie hereinkommen ...

Sie werden weiterhin subscribe verwenden, aber mit pipe(take(1)) erhalten Sie einen einzigen Wert.

z.B. myObs$.pipe(take(1)).subscribe(value => alert(value));

Siehe auch: Vergleich zwischen first(), take(1) oder single()


Längere Antwort:

Die allgemeine Regel ist, dass Sie immer nur mit subscribe() einen Wert aus einem Observable erhalten sollten.

(oder asynchrone Pipe bei Verwendung von Angular)

BehaviorSubject hat definitiv seinen Platz, und als ich mit RxJS anfing, habe ich oft bs.value() gemacht, um einen Wert herauszufinden. Da sich Ihre RxJS-Streams in der gesamten Anwendung ausbreiten (und genau das möchten Sie!), Wird es immer schwieriger, dies zu tun. Oft wird tatsächlich .asObservable() angezeigt, das verwendet wird, um den zugrunde liegenden Typ zu "verbergen", um zu verhindern, dass jemand .value() verwendet - und zuerst dies wird gemein erscheinen, aber Sie werden verstehen, warum es im Laufe der Zeit getan wird. Außerdem benötigen Sie früher oder später einen Wert, der ist nicht a BehaviorSubject ist, und es gibt keine Möglichkeit, dies zu ändern.

Zurück zur ursprünglichen Frage. Vor allem, wenn Sie nicht mit einem BehaviorSubject 'schummeln' wollen.

Der bessere Ansatz ist immer, subscribe zu verwenden, um einen Wert herauszufinden.

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

OR

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

Der Unterschied zwischen diesen beiden Werten, first(), ist ein Fehler , wenn der Stream bereits abgeschlossen ist , und take(1) gibt keine Observablen aus, wenn der Stream abgeschlossen ist abgeschlossen oder hat keinen sofort verfügbaren Wert.

Hinweis: Dies wird als bessere Vorgehensweise angesehen, auch wenn Sie ein BehaviorSubject verwenden.

Wenn Sie jedoch den obigen Code ausprobieren, bleibt der 'Wert' der beobachtbaren Datei innerhalb des Abschlusses der Subskriptionsfunktion hängen, und Sie benötigen ihn möglicherweise im aktuellen Bereich. Ein Weg, dies zu umgehen, wenn Sie es wirklich müssen, ist folgender:

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);

Es ist wichtig zu beachten, dass der obige Abonnement-Aufruf nicht wait auf einen Wert wartet. Wenn nichts sofort verfügbar ist, wird die Abonnementfunktion niemals aufgerufen, und ich habe den Abmeldeanruf absichtlich dort abgelegt, um zu verhindern, dass er später angezeigt wird.

Das sieht also nicht nur merkwürdig ungeschickt aus - es funktioniert nicht für etwas, das nicht sofort verfügbar ist, wie ein Ergebniswert aus einem http-Aufruf, sondern es funktioniert tatsächlich mit einem Verhaltensthema (oder, was noch wichtiger ist, etwas, das * ist). Upstream und bekannt als BehaviorSubject ** oder als combineLatest mit zwei BehaviorSubject Werten). Und auf keinen Fall (obs$ as BehaviorSubject) - ugh!

Dieses vorherige Beispiel wird im Allgemeinen immer noch als schlechte Praxis angesehen - es ist ein Durcheinander. Ich mache den vorherigen Codestil nur, wenn ich sehen möchte, ob ein Wert sofort verfügbar ist und in der Lage ist, zu erkennen, ob er nicht verfügbar ist.

Bester Ansatz

Sie sind weitaus besser dran, wenn Sie alles so lange wie möglich als beobachtbar halten können - und nur dann abonnieren, wenn Sie den Wert unbedingt benötigen - und nicht versuchen, einen Wert in einen enthaltenen Bereich zu "extrahieren", wie ich es tue über.

z.B. Nehmen wir an, wir möchten einen Bericht über unsere Tiere erstellen, wenn Ihr Zoo geöffnet ist. Sie könnten denken, Sie möchten den 'extrahierten' Wert von zooOpen$ Wie folgt:

Schlechter Weg

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!');
   }
}

Warum ist das SO BAD

  • Was ist, wenn zooOpen$ Von einem Webservice stammt? Wie wird das vorherige Beispiel jemals funktionieren? Es spielt eigentlich keine Rolle, wie schnell Ihr Server ist - mit dem obigen Code würden Sie niemals einen Wert erhalten, wenn zooOpen$ Ein beobachtbarer http wäre!
  • Was ist, wenn Sie diesen Bericht "außerhalb" dieser Funktion verwenden möchten? Sie haben jetzt alert für diese Methode gesperrt. Wenn Sie den Bericht an einer anderen Stelle verwenden müssen, müssen Sie ihn duplizieren!

Gute Möglichkeit

Anstatt zu versuchen, auf den Wert in Ihrer Funktion zuzugreifen, betrachten Sie stattdessen eine Funktion, die ein neues Observable erstellt und es nicht einmal abonniert!

Stattdessen wird ein neues Observable zurückgegeben, das "außerhalb" konsumiert werden kann.

Indem Sie alles als Observables speichern und switchMap verwenden, um Entscheidungen zu treffen, können Sie neue Observables erstellen, die selbst die Quelle anderer Observables sein können.

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!');
      }

   });
 }

Das obige erzeugt ein neues Observable , so dass wir es woanders ausführen können und wenn wir möchten, mehr Pipe-Effekte erzeugen können.

 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()));

Beachte das Folgende:

  • Ich melde mich erst an, wenn ich es unbedingt muss - in diesem Fall liegt das außerhalb der Funktion
  • Die beobachtbare 'treibende' Variable ist zooOpen$ Und verwendet switchMap, um zu einer anderen beobachtbaren Variable 'umzuschalten', die letztendlich die von getZooReport() zurückgegebene ist.
  • Die Art und Weise, wie dies funktioniert, wenn sich zooOpen$ Jemals ändert, löscht alles und beginnt erneut innerhalb des ersten switchMap. Lesen Sie mehr über switchMap.
  • Hinweis: Der Code in switchMap muss eine neue beobachtbare Variable zurückgeben. Sie können eine schnell mit of('hello') erstellen - oder eine andere beobachtbare Variable wie combineLatest zurückgeben.
  • Ebenso: map muss nur eine reguläre Zeichenfolge zurückgeben.

Sobald ich anfing, mir eine mentale Notiz zu machen, dass ich mich nicht anmelden sollte, bis ich hatte schrieb, begann ich plötzlich, viel produktiver, flexibler, sauberer und wartbarer Code zu schreiben.

Noch eine letzte Anmerkung: Wenn Sie diesen Ansatz mit Angular= verwenden, können Sie den obigen Zoo-Bericht auch ohne eine einzige subscribe mithilfe der Pipe | async Erstellen Tolles Beispiel für das Prinzip "Erst abonnieren, wenn Sie es haben" in der Praxis.

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

und in deiner Vorlage:

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

Siehe auch meine Antwort hier:

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

Ebenfalls oben nicht erwähnt, um Verwirrung zu vermeiden:

  • tap() kann manchmal nützlich sein, um 'den Wert aus einer beobachtbaren herauszuholen'. Wenn Sie mit diesem Operator nicht vertraut sind, lesen Sie ihn ein. RxJS verwendet "Pipes" und ein tap() -Operator ist eine Möglichkeit, "in die Pipe zu tippen", um zu sehen, was sich dort befindet.
7
Simon_Weaver

Verwenden Sie den Observable-Konstruktor, um einen beobachtbaren Stream eines beliebigen Typs zu erstellen. Der Konstruktor verwendet als Argument die Abonnentenfunktion, die ausgeführt wird, wenn die subscribe () -Methode des Observable ausgeführt wird. Eine Abonnentenfunktion empfängt ein Observer-Objekt und kann Werte für die next () -Methode des Observers veröffentlichen. Versuchen Sie dies

@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

Nicht sicher, ob Sie das suchen. Schreiben Sie dieses Verhalten wahrscheinlich in einen Dienst. Deklarieren Sie es als privat und machen Sie nur den von Ihnen festgelegten Wert verfügbar. Etwas wie das

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

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

Auf diese Weise haben die externen Benutzer nur das Abonnement für das behaviorSubject, und Sie können den gewünschten Wert im Service einstellen.

0
Joey587