4
votes

RxJs / Chargement angulaire à partir de plusieurs requêtes HTTP et mise à jour du modèle à partir de chaque requête

J'utilise Angular / RxJS depuis quelques semaines et j'ai divers modèles qui sont construits à partir de plusieurs requêtes REST que j'ai jusqu'à présent réalisées en utilisant switchMap (). Voici un exemple artificiel simple (stackblitz: https://stackblitz.com/edit/angular-o1djbb a>):

// first value emitted:
{ itemName: 'foo', details: null }
// second value emitted:
{ itemName: 'foo', details: 'Some details about foo' }

Et un modèle simple:

<p>
  item: {{order && order.itemName}}
</p>
<p>
  details: {{order && order.details}}
</p>

Cela fonctionne mais les informations de commande ne sont pas rendues avant les deux demandes terminées. Ce que je souhaiterais, c'est que le itemName soit rendu dès qu'il est disponible, puis les détails à rendre lorsqu'ils deviennent disponibles. Profitant ainsi des multiples valeurs que les observables peuvent émettre. Par exemple:

import { Component, OnInit, } from '@angular/core';
import { Observable, of } from 'rxjs';
import { delay, switchMap } from 'rxjs/operators';

interface Order {
  id: string;
  itemName: string;
  details?: string;
}

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
  order: Order;

  ngOnInit() {
    this.getOrderFromApi(123)
      .subscribe(item => this.order = item);
  }

  getOrderFromApi(id): Observable<Order>  {
    const item$ = this.getItemName(id);
    const fullItem$ = item$.pipe(
      switchMap(n => {
        console.log(`Got name: '${n}''`);
        return this.getDetails(n);
      },
        (nameReq, detailsReq) => ({ id: '123', itemName: nameReq, details: detailsReq })
      ));
    return fullItem$;
  }

  getItemName(id): Observable<string> {
    return this.fakeXhr('foo');
  }

  getDetails(itemName): Observable<string> {
    console.log(`Got details '${itemName}''`)
    return this.fakeXhr('Some details about foo');
  }

  fakeXhr(payload: any) {
    return of(payload)
      .pipe(delay(2000));
  }
}

Je me rends compte que je pourrais probablement y parvenir avec un BehaviourSubject ou en utilisant Redux (comme je l'ai fait dans le passé avec React), mais je pense qu'il existe une solution simple que je peux pas tout à fait à cause de la nouveauté de tout cela pour moi.


0 commentaires

4 Réponses :


0
votes

Peut-être que votre meilleur choix est de remplacer mergeMap () par switchMap () ?

https://blog.angular-university.io/rxjs- mappage d'ordre supérieur /

Comme nous pouvons le voir, les valeurs des Observables sources fusionnées s'affichent immédiatement dans la sortie. Le résultat Observable ne sera pas complété jusqu'à ce que tous les observables fusionnés soient terminés.

Je consulterais certainement ces articles:


1 commentaires

Un substitut direct ne fonctionnera certainement pas: stackblitz.com/edit/angular-f8canj?file=src/app/... Foncez ce stackblitz et montrez-nous comment!



0
votes

Au lieu de conserver votre élément complet à la fin du flux observable (c'est-à-dire dans votre bloc .subscribe ), vous pouvez stocker les valeurs reçues dans les parties respectives de votre code, c'est-à-dire après leur disponibilité :

this.getOrderFromApi(123)
      .subscribe(item => this.order.details = item.details);

... et ensuite dans votre bloc sibscription vous n'auriez qu'à stocker les informations avancées:

 switchMap(n => {
        console.log(`Got name: '${n}''`);
        this.order.name = n;
        return this.getDetails(n);
      },

Evidemment, cela nécessite vous devez initialiser votre attribut «commande» en conséquence, par exemple avec order: Order = {}; ou order: Order = new Order (); .

Fondamentalement, l'idée est de restructurer votre code s.t. votre flux observable émet des valeurs partielles (les attributs de votre commande) au lieu d'un objet à part entière.


1 commentaires

Cela pourrait fonctionner dans cet exemple artificiel, mais en réalité, getOrderFromApi () serait dans un service et le seul moyen d'atteindre ce qui précède serait de lui passer une référence à un objet getOrderFromApi (this.order) < / i> qui n'est pas vraiment ce que je cherchais. Je veux que plusieurs valeurs soient émises par l'Observable retourné.



0
votes

Une solution que j'ai trouvée était de simplement créer un Observable et d'appeler next (). Je serais intéressé de connaître votre opinion à ce sujet.

getOrder(id) {
    const ob$ = new Observable(subscriber => {
      const item$ = this.getItem(id)
        .pipe(
          tap(o => subscriber.next(({ id: id, itemName: o.itemName, details: null }))),
          switchMap(o => this.getDetails(o.itemName),
            (o, d) => ({ id: id, itemName: o.itemName, details: d.details })
          ),
          tap(a => subscriber.next(a))
        );
      item$.subscribe(a => {
        subscriber.complete();
      });
    });

    return ob$;
  }

Stackblitz: https://stackblitz.com/edit/angular-mybvli


0 commentaires

2
votes

Utilisez expand pour émettre les données dès la première demande et faites ensuite la deuxième demande.

expand appellera la requête interne de manière récursive, obtenant la commande de la dernière requête en entrée, nous n'exécutons donc la deuxième requête que lorsque la commande code> n'a pas de détails et termine la récursion autrement avec un EMPTY Observable.

import { Observable, EMPTY } from 'rxjs';
import { map, expand } from 'rxjs/operators';

getOrderFromApi(id): Observable<Order> {
  return this.getItemName(id).pipe(
    map(itemName => ({ id, itemName, details: null } as Order)),
    expand(order => order.details
      ? EMPTY
      : this.getDetails(order.itemName).pipe(
        map(details => ({ id, itemName: order.itemName, details } as Order))
      )
    )
  );
}

https://stackblitz.com/edit/angular-umqyem

L'Observable retourné émettra:

  1. {"id": 123, "itemName": "foo", "details": null}
  2. {"id": 123, "itemName": "foo", "details": "Quelques détails sur foo"}


2 commentaires

Je marque cela comme la réponse car je pense que c'est la solution la plus élégante (du moins actuellement). Nous pourrions aller plus loin et utiliser l ' index de expand quelque chose comme ceci: stackblitz.com/edit/angular-btso9u?file=src/app/...


Merci, expand est également bien adapté pour exécuter un nombre inconnu de requêtes, comme lorsque vous traitez avec la pagination. stackoverflow.com/a/54293624/9423231