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 :
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));
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).
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,
})
)
);
}
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));
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)
Voici le résultat:
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
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.
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:
- Je vais utiliser ce genre de pattern sur des app qui ne sont pas encore dans les derniĂšres versions d'Angular.
- Il suffit de faire un toSignal si besoin
- 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.
Top comments (0)