1
votes

RXJS évite de répéter l'appel si la demande est en cours

Il y a des données de configuration dans le projet qui sont utilisées par plusieurs composants et services. L'idée est de créer un service qui ne récupère qu'une seule fois les données de l'API et les met en cache à chaque fois que cela est nécessaire.

private getConfig(): Observable<InitConfig> {
  return this.http.get('public/config/initconfig')
    .pipe(
      tap((config: InitConfig) => {
        this.config = config; // set the config
      }),
    );
}


public getInitConfig(): Observable<InitConfig> {
  if ( _.isEmpty(this.config) ) {
    return this.getConfig();
  } else {
    return of(this.config);
  }
}

Le problème est lorsque la demande d'obtention est en cours et que les données ne sont pas encore mises en cache. Si un autre consommateur appelle getInitConfig () à ce moment, je reçois une requête get dupliquée.


0 commentaires

5 Réponses :


0
votes

Vous pouvez le faire avec shareReplay(1)

private getConfig(): Observable<InitConfig> {
  return this.http.get('public/config/initconfig')
    .pipe(
      shareReplay(1)
    );
}

Exemple: https://stackblitz.com/edit/angular-ivy-b2osdk p >


2 commentaires

essayé d'ajouter l'opérateur shareReplay (1) et j'ai toujours des demandes répétées.


@sandum il doit y avoir une autre erreur de votre côté. Voici une démo pour prouver que cela fonctionne comme je l'ai dit: stackblitz.com/edit/angular-ivy- b2osdk L'appel http n'est effectué qu'une seule fois!



0
votes

Les promesses fonctionneront exactement comme vous le souhaitez. c'est le moyen le plus simple et le plus élégant

private getConfig(): Promise<InitConfig> {
  return this.http.get('public/config/initconfig').toPromise();
}


public getInitConfig(): Promise<InitConfig> {
  return this.config = this.config || this.getConfig();
}


2 commentaires

Quel est le problème avec cette solution? En dehors de la modification de la signature de la méthode getInitConfig ?


Le problème est de transformer les observables en promesses. Tout ce que vous faites est de supprimer des fonctionnalités. Tout ce que l'OP veut peut être réalisé avec Observables, et l'approche Observable permet également une meilleure maintenabilité et une meilleure modularité à l'avenir.



3
votes

Vous avez besoin d'une observable privée qui indique si la configuration a déjà été récupérée, si elle ne l'a pas été (ce qui signifie que c'est le premier abonnement), initialisez-la et retournez-la de toute façon.

Tout d'abord, ajoutez une configuration privée $: Observable à votre service.
Ensuite, utilisez shareReplay (1) afin de partager le résultat entre les abonnés.

Cela devrait ressembler à ceci:

  ngOnInit(): void {
    this.configService.getInitConfig().subscribe(() => console.log('Config Subscription - 1'))
    this.configService.getInitConfig().subscribe(() => console.log('Config Subscription - 2'))
    this.configService.getInitConfig().subscribe(() => console.log('Config Subscription - 3'))
  }

Vous pouvez le tester en vous abonnant plusieurs fois et voir la config récupérée être journalisée une seule fois:

  private config$: Observable<InitConfig>;

  getInitConfig(): Observable<InitConfig> {
    if (!this.config$) {
      this.config$ = this.http
        .get("https://jsonplaceholder.typicode.com/users/1")
        .pipe(
          tap(() => console.log("config fetched")),
          shareReplay(1)
        );
    }

    return this.config$;
  }

Vous pouvez voir le code complet dans ce stackblitz


0 commentaires

3
votes

Le problème habituel avec votre approche est que chaque fois que vous effectuez un appel HTTP, cela vous donnera une nouvelle observable . Cette nouvelle observable est de nature par défaut FROID . Cela signifie que vous devez vous y abonner pour obtenir le résultat. Si vous l'appelez plusieurs fois, vous obtiendrez plusieurs observables et vous finirez par faire plusieurs requêtes HTTP.

SOLUTION

Vous devriez utiliser un observable unique et abonnez-vous plusieurs fois avec l'opérateur Partager . L'opérateur Partager rendra l'observable HOT après avoir effectué le premier appel HTTP. Dès qu'il devient chaud, si un autre abonné est trouvé, plutôt que de faire un autre appel HTTP, il renverra simplement les données.

en service,

private publicObservable = this.getConfig();          // publicObservable single observable  

private getConfig(): Observable<InitConfig> {
  return this.http.get('public/config/initconfig')
    .pipe(
        share()                                      // share operator
    );
}


public getInitConfig(): Observable<InitConfig> {
     return this.service.publicObservable;
}

Dans Demo, je m'abonne 8 fois au même HTTP. Si je supprime, partager opérateur, vous pouvez voir dans l'onglet RÉSEAU , 8 appels seront effectués

Avec l'opérateur partager , une seule demande sera effectuée.

DEMO

J'espère que cela vous aidera!


2 commentaires

En général, votre réponse est correcte. Mais j'essaie d'expliquer pourquoi shareReplay (1) convient mieux ici. Dans votre exemple, les 8 abonnés ne partagent la même valeur que si les abonnements sont effectués avant ou pendant l'exécution de l'opération asynchrone. Si vous vous abonnez ultérieurement à votre service, les mêmes données sont à nouveau demandées. Vous pouvez le voir dans l'onglet réseau de votre exemple modifié: stackblitz. com / edit / share-rxjs-operator-live-example-kgpudy C'est pourquoi je recommande shareReplay (1) pour cela. Cela rend également l'observable chaud et fournit en outre la dernière valeur émise aux nouveaux abonnés.


Oui @ enno.void. J'étais complètement d'accord! Aucun doute là dessus.



0
votes

Voici une approche légèrement différente qui ressemble un peu à un système de gestion d'état Redux.

private loading = false;
private loaded = false;
private config$ = new BehaviorSubject<InitConfig>(null);

public getConfig() {
  this.requestConfig();
  return this.config$.asObservable();
}

public requestConfig() {
  if (loading || loaded) {
    return;
  }
  this.loading = true;
  this.http.get('public/config/initconfig')
    .pipe(
      take(1),
      tap((config: InitConfig) => {
        this.config$.next(config);
        this.loaded = true;
        this.loading = false;
      }),
    ).subscribe();
}

Cela peut valoir la peine de chercher à implémenter quelque chose comme NgRx, car il est construit précisément pour cela sorte de gestion d'état plus complexe.


0 commentaires