web-dev-qa-db-fra.com

Comment obtenir la valeur actuelle de RxJS Subject ou Observable?

J'ai un service 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);
    });
  }
}

Tout fonctionne très bien. Mais j’ai un autre composant qui n’a pas besoin de s’abonner, il doit simplement obtenir la valeur actuelle d’isLoggedIn à un moment donné. Comment puis-je faire ceci?

120
Baconbeastnz

Subject ou Observable n'a pas de valeur actuelle. Lorsqu'une valeur est émise, elle est transmise aux abonnés et la variable Observable est utilisée.

Si vous voulez avoir une valeur actuelle, utilisez BehaviorSubject qui est conçu pour cela. BehaviorSubject conserve la dernière valeur émise et l’émet immédiatement aux nouveaux abonnés.

Il a également une méthode getValue() pour obtenir la valeur actuelle.

219

La seule façon dont vous devriez obtenir des valeurs "hors" d'un Observable/Sujet est de vous abonner!

Si vous utilisez getValue(), vous faites quelque chose d'impératif dans le paradigme déclaratif. C'est là comme une trappe d'évacuation, mais 99,9% du temps, vous ne devriez PAS utiliser getValue(). Il y a quelques choses intéressantes que getValue() va faire: Cela jettera une erreur si le sujet a été désabonné, cela vous empêchera d'obtenir une valeur si le sujet est mort parce qu'il est erroné, etc. Mais là encore, c'est là comme une trappe de secours pour de rares circonstances.

Il existe plusieurs manières d'obtenir la dernière valeur d'un sujet ou d'un observable de manière "Rx-y":

  1. Utilisation de BehaviorSubject: Mais s’y abonnant réellement. Lorsque vous vous abonnez pour la première fois à BehaviorSubject, il envoie de manière synchrone la valeur précédente reçue ou avec laquelle il a été initialisé.
  2. Utilisation de ReplaySubject(N): Ceci mettra en cache les valeurs N et les rediffusera aux nouveaux abonnés.
  3. A.withLatestFrom(B): utilisez cet opérateur pour obtenir la valeur la plus récente de la variable observable B lorsque la variable observable A émet. Vous donnera les deux valeurs dans un tableau [a, b].
  4. A.combineLatest(B): utilisez cet opérateur pour obtenir les valeurs les plus récentes de A et B à chaque fois que A ou B est émis. Vous donnera les deux valeurs dans un tableau.
  5. shareReplay(): effectue une multidiffusion observable via une ReplaySubject, mais vous permet de réessayer l'observable en cas d'erreur. (Fondamentalement, il vous donne ce comportement de mise en cache prometteur-y).
  6. publishReplay(), publishBehavior(initialValue), multicast(subject: BehaviorSubject | ReplaySubject), etc.: autres opérateurs utilisant BehaviorSubject et ReplaySubject. Différents parfums de la même chose, ils diffusent essentiellement la source observable en canalisant toutes les notifications par le biais d'un sujet. Vous devez appeler connect() pour vous abonner à la source avec le sujet.
98
Ben Lesh

J'ai eu une situation similaire où les abonnés en retard s'abonnent au sujet après que sa valeur soit arrivée. 

J'ai trouvé ReplaySubject qui est similaire à BehaviorSubject fonctionne comme un charme dans ce cas . Et voici un lien vers une meilleure explication: http://reactivex.io/rxjs/manual/overview.html# replaysubject

4
Kfir Erez

J'ai rencontré le même problème dans les composants enfants, où il devait initialement avoir la valeur actuelle de Subject, puis souscrire à Subject pour écouter les modifications. Je maintiens simplement la valeur actuelle dans le service afin qu'elle soit accessible aux composants, par exemple. :

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

Un composant qui a besoin de la valeur actuelle peut alors y accéder à partir du service, à savoir:

sessionStorage.isLoggedIn

Pas sûr que ce soit la bonne pratique :)

3
Molp Burnbright

Une réponse similaire regardant a été votée. Mais je pense pouvoir justifier ce que je suggère ici pour des cas limités.


S'il est vrai qu'un observable n'a pas de valeur current , il aura très souvent une valeur immédiatement disponible . Par exemple, avec les magasins redux/flux/akita, vous pouvez demander des données à un magasin central, en fonction d'un certain nombre d'observables. Cette valeur sera généralement immédiatement disponible. 

Si tel est le cas, lorsque vous subscribe, la valeur reviendra immédiatement.

Supposons donc que vous ayez eu un appel à un service et que vous souhaitiez obtenir la dernière valeur de quelque chose de votre magasin, qui pourrait ne pas émettre:

Vous pouvez essayer de faire ceci (et vous devriez autant que possible garder les choses "dans des tuyaux"): 

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

                        // we have serviceCallResponse and customer 
                     });

Le problème, c’est qu’elle bloquera jusqu’à ce que l’observable secondaire émette une valeur, qui pourrait ne jamais être.

J'ai récemment eu besoin d'évaluer un observable uniquement si une valeur était immédiatement disponible , et plus important encore, je devais être capable de détecter si ce n'était pas le cas. J'ai fini par faire ceci:

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

Notez que pour tout ce qui précède, j'utilise subscribe pour obtenir la valeur (comme le dit @Ben). Ne pas utiliser une propriété .value, même si j'avais une BehaviorSubject.

1
Simon_Weaver
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
// .................
// .................
// .................

Vous pouvez consulter l'article complet sur la manière de le mettre en œuvre à partir d'ici . https://www.imkrish.com/how-to-get-current-value-of-observable-in-a-clean-way/

0
Keerati Limkulphong

Bien que cela puisse paraître excessif, il ne s'agit que d'une autre solution "possible" pour conserver le type Observable et réduire le passe-partout ...

Vous pouvez toujours créer un getter extension pour obtenir la valeur actuelle d'un observable.

Pour ce faire, vous devez étendre l'interface Observable<T> dans un fichier de déclaration de typages global.d.ts. Ensuite, implémentez le getter extension dans un fichier observable.extension.ts et incluez enfin à la fois les saisies et le fichier d’extension dans votre application.

Vous pouvez vous référer à ceci StackOverflow Answer pour savoir comment inclure les extensions dans votre application 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
j3ff