DEV Community

Cover image for Angular: tracking fin des requĂȘtes CRUD sur une liste (RxJs groupBy 🙏)
Romain Geffrault
Romain Geffrault

Posted on

1 1 1 1

Angular: tracking fin des requĂȘtes CRUD sur une liste (RxJs groupBy 🙏)

L'article montre comment appliquer une requĂȘte API Ă  chaque entitĂ© d'une liste, afficher son statut de chargement et exĂ©cuter les requĂȘtes en parallĂšle.

Tu trouveras un exemple que tu pourras utiliser dans tes applications.

Voici un exemple :

Une liste de données avec des items qui ont été modifiés et d'autres supprimé grùce à groupBy RxJs

Pour faire ça facilement, je vais te présenter l'opérateur groupBy que l'on va utiliser avec un autre opérateur custom que j'utilise fréquemment "statedStream".

CrĂ©er l'opĂ©rateur "statedStream" pour connaĂźtre le statut de chargement d'une requĂȘte

L'opĂ©rateur statedStream permet de connaĂźtre le statut de chargement d'une requĂȘte asynchrone, je l'utilise la plupart du temps lors d'un appel API.

Le fonctionnement est similaire Ă  celui de httpRessource d'Angular, sauf que l'on reste dans le domaine des observables.

updateItem$
  .pipe(
    switchMap((updateItem) => // everytime, updateItem$ emit a new value, it cancels the existing api call, and create a new API
      statedStream(updateApiCall$(updateItem), updateItem)
    )
  )
  .subscribe((data) => console.log('data', data));

Enter fullscreen mode Exit fullscreen mode

Au lieu d'attendre de recevoir une unique valeur lorsque l'appel API se termine, statedStream va Ă©mettre une premiĂšre valeur en indiquant que la requĂȘte est en train de charger (isLoading: true).

Les logs de réponses du statedStream RxJs

Voici une partie du code de statedStream :

export function statedStream<T>(
  toCall: Observable<T>,
  initialValue: T
): Observable<SatedStreamResult<T>> {
  return toCall.pipe(
    map(
      (result) =>
        ({
          isLoading: false,
          isLoaded: true,
          hasError: false,
          error: undefined,
          result,
        } satisfies SatedStreamResult<T>)
    ),
    startWith({
      isLoading: true,
      isLoaded: false,
      hasError: false,
      error: undefined,
      result: initialValue,
    }),
    catchError((error) =>
      of({
        isLoading: false,
        isLoaded: false,
        hasError: true,
        error,
        result: initialValue,
      })
    )
  );
}
Enter fullscreen mode Exit fullscreen mode

A noter que si tu n'as pas l'habitude de travailler avec des streams d'observables, si l'appel api retourne une erreur, ton stream s'arrĂȘte et n'Ă©coutera plus les prochaines Ă©missions de la source (ici updateItem$).

GrĂące Ă  la fonction statedStream, les erreurs sont "catch" et rĂ©cupĂ©rĂ© dans le rĂ©sultat. Cela permet de ne pas rompre le stream lors d'une erreur API et de continuer Ă  Ă©mettre de nouvelles requĂȘtes.

Voici un lien stackblitz pour voir cette fonction en détail

Débloquer le potentiel titanesque de l'opérateur groupBy

Je ne sais pas si tu as déjà utilisé l'opérateur groupBy de RxJs ? Perso, quand j'ai lu la doc la premiÚre fois, j'ai pas compris. La dixiÚme fois non plus... Mais grùce à cet exemple, j'ai compris. Depuis, je comprends ;D

Si tu souhaites lire la doc, n'hésite pas, il y a aussi un exemple sur stackblitz.

Grosso modo, on réutilise l'exemple de statedStream et on l'ajoute dans le stream du groupBy:

updateItem$
  .pipe(
    groupBy((updateItem) => updateItem.id), // create a group for each unique id
    mergeMap((group$) => {
      console.log('group$', group$.key);
      return group$.pipe(
        switchMap((updateItem) =>
          statedStream(updateApiCall$(updateItem), updateItem)
        )
      );
    })
  )
  .subscribe((data) => console.log('Received:', data));
Enter fullscreen mode Exit fullscreen mode

Ensuite, on Ă©met quelques update et lĂ , tu vas comprendre. On Ă©met 2 update successivement, puis un troisiĂšme update aprĂšs 5s.

console.log("emit updateItem first time", 'id: 4')
updateItem$.next({
  id: '4',
  name: 'Romain Geffrault 4',
});

console.log("emit updateItem first time", 'id: 5')
updateItem$.next({
  id: '5',
  name: 'Romain Geffrault 5',
});


setTimeout(() => {
  console.log("emit updateItem second time", 'id: 4')
  updateItem$.next({
    id: '4',
    name: 'Romain Geffrault 4, updated twice',
  });
}, 5000)
Enter fullscreen mode Exit fullscreen mode

Voici le résultat:

Logs de réponses du statedStream et groupBy RxJs

GrĂące Ă  l'opĂ©rateur groupBy, c'est simple de lancer plusieurs requĂȘtes API en parallĂšle.

Voici le lien pour voir cette beauté en action.

Dans l'exemple, j'ai groupé par id, qui un cas basique, mais on peut pousser plus loin la notion de grouper.

Afficher une liste d'entités avec des statuts de chargement réactif sur Angular

Reactive Data List with Detailed Entity Status Tracking with groupBy RxJs Angular

Je vais ĂȘtre pragmatique et te prĂ©senter une implĂ©mentation qui se rapproche d'un cas rĂ©el que tu pourras rĂ©utiliser facilement.

Managing Entity Status in Reactive Data Lists with RxJS

Malheureusement, le lien stackblitz ne marche pas, mais voici le repo du code que tu peux cloner pour essayer.

J'ai utilisé NodeJs v.20

npm i
ng serve

Les pages qui nous intéressent sont :
src\app\features\data-list\data-list.component.ts
& src\app\features\data-list\data-list.component.html

J'ai ajouté pas mal de commentaires pour t'expliquer le fonctionnement de certaines fonctions RxJs si tu n'as pas l'habitude.

J'ai utilisé ici une approche déclarative/réactive. Car c'est ma façon de faire avec ces nombreux avantages.

Tu remarqueras que j'ai gĂ©rĂ© le cas oĂč on laisse les appels API se terminer avant d'unsubscribe les stream (comme ça pas de memoryleak et pas de cas bizarre).

J'adore cet exemple, mais je trouve qu'il peut ĂȘtre encore amĂ©liorĂ©.

Par exemple, je dois répéter plusieurs fois les types de données pour TS.
MĂȘme si ce n'est pas trĂšs compliquĂ© Ă  rajouter, je n'ai pas gĂ©rĂ© le cas de garder l'affichage de la liste existante lors de la navigation, je n'ai pas mis la possibilitĂ© d'ajouter facilement des sĂ©lecteurs...

Un autre point qui peut ĂȘtre un peu gĂȘnant, c'est que malgrĂ© tout ces bouts de codes prennent de l'espace, et nuisent un peu Ă  la visibilitĂ© globale du composant.

Une solution peut ĂȘtre de dĂ©couper les diffĂ©rents cas qui se trouvent dans le scan, en fonction, ce qui s'apparente Ă  des reducers. Mais ça peut ĂȘtre un peu contraignant pour rĂ©cupĂ©rer les bons types, lĂ  oĂč typescript les devines (infer).

On peut aussi imaginer qu'on veuille appliquer une mĂȘme action Ă  plusieurs items Ă  la fois (bulkEdit...).

J'ai pris en compte tous ces manques et bien d'autres encore, je suis en train de créé un petit outil expérimental pour le moment qui va me permettre d'implémenter ces mécanismes de façon déclarative.

Ca se rapproche d'un outil de server-state-management, comme pourrait le faire TanStackQuery. Ca demande encore de la réflexion, mais j'ai hùte de te présenter le résultat.

Si t'as des questions, n'hésite pas, ou si tu souhaites en discuter n'hésite pas à commenter, je me ferai un plaisir de te répondre de mon mieux.

Ps: Je n'ai pas utilisé les signal, car:

  1. Je vais utiliser ce genre de pattern sur des app qui ne sont pas encore dans les derniĂšres versions d'Angular.
  2. Il suffit de faire un toSignal si besoin
  3. Surtout que les trigger des updates/deletes sont des événements et pas des états, ce que gÚre parfaitement les observables, mais pas les signal.

Heroku

Amplify your impact where it matters most — building exceptional apps.

Leave the infrastructure headaches to us, while you focus on pushing boundaries, realizing your vision, and making a lasting impression on your users.

Get Started

Top comments (0)

5 Playwright CLI Flags That Will Transform Your Testing Workflow

  • 0:56 --last-failed
  • 2:34 --only-changed
  • 4:27 --repeat-each
  • 5:15 --forbid-only
  • 5:51 --ui --headed --workers 1

Learn how these powerful command-line options can save you time, strengthen your test suite, and streamline your Playwright testing experience. Click on any timestamp above to jump directly to that section in the tutorial!

👋 Kindness is contagious

Engage with a wealth of insights in this thoughtful article, valued within the supportive DEV Community. Coders of every background are welcome to join in and add to our collective wisdom.

A sincere "thank you" often brightens someone’s day. Share your gratitude in the comments below!

On DEV, the act of sharing knowledge eases our journey and fortifies our community ties. Found value in this? A quick thank you to the author can make a significant impact.

Okay