Il s'agit d'une librairie très puissante mais mal utilisée, elle peut conduire à des soucis de performance, et à des anomalies difficile à détecter et à corriger.
Dans ce billet de blog, je vous propose quelques conseils pour s'en sortir avec la librairie RxJS.
TL;DR
Éviter la surcharge de la méthode subscribe
Utiliser les pipes RxJS pour transformer les données et utiliser le pipe async pour éviter les souscriptions manuelles.
❌ Don't
let value;
myDataSource$.subscribe(myNewValue => {
value = myNewValue + 1;
});
<div>{{value}}</div>
✔️ Do
let value;
let value$ = myDataSource$.pipe(map(myNewValue => myNewValue + 1));
<div>{{ value | async }}</div>Se prémunir des fuites mémoires
Ne pas se désabonner de souscriptions sur des flux de données peut provoquer des fuites mémoire !
❌ Don't
const interval$ = interval(1000);
const subscription = interval$.subscribe(i => console.log(i));
// Continue à produire des valeurs même lorsque que le composant est détruit !
✔️ Do
const destroy$ = new Subject();
ngOnInit() {
const interval$ = interval(1000);
// Se désabonne automatiquement lors de la destruction du composant
interval$.pipe(takeUntil(this.destroy$)).subscribe(i => console.log(i));
}
ngOnDestroy() => this.destroy$.next(true);
Dans une application Angular, éviter également de faire appel à des méthodes directement dans le template (hors traitements asynchrones). Les méthodes dans les templates sont appelés énormément de fois ce qui impacte négativement la performance de votre application.
Une méthode dans un template peut être appelé plus d'une dizaine de fois en seulement quelques secondes ! Prudence !
Privilégier donc le stockage du résultat de votre méthode dans une variable locale de votre composant, puis utilisez la dans votre template.
Éviter les subscribe imbriqués
Les subscribe imbriqués devraient être évités autant que possible : ils rendent le code illisible, complexe et peuvent introduire des effets de bord.
❌ Don't
this.route.params
.pipe(map(params => params.id))
.subscribe(id => {
this.userService.fetchById(id).subscribe(user => this.user = user);
});
✔️ Do
this.route.params
.pipe(
map(params => params.id),
switchMap(id => this.userService.fetchById(id))
)
.subscribe(user => this.user = user);
L'opérateur switchMap permet de "switcher" (basculer) d'un Observable vers un autre Observable. Dans le cas ci-dessus, on récupère le paramètre id de la route courante puis on "switch" vers l'Observable récupéré par la méthode userService.fetchById. Cela permet d'éviter une imbrication inutile et permet de clarifier le code. Autre avantage : l'opérateur switchMap clot le premier Observable permettant ainsi d'éviter des fuites mémoires.
Partager les souscriptions
Partager les souscriptions plutôt que de redéclencher des traitements superflus (surtout pour les appels HTTP). Utiliser les pipes share ou shareReplay.
❌ Don't
<div [users]="users$ | async"></div>
<div>{{ numberOfUsers$ | async }}</div>
const users$ = this.http.get('/api/users’);
const numberOfUsers$ = this.users$.pipe(map(users => users.length);
// Va appeler de nouveau l'endpoint /users !!
✔️ Do
<div [users]="users$ | async"></div>
<div>{{ numberOfUsers$ | async }}</div>
const users$ = this.http.get('/api/users').pipe(shareReplay(1));
const numberOfUsers$ = this.users$.pipe(map(users => users.length);
// Réutilise la valeur de l'observable users$Apprendre à penser réactif
- Utiliser la programmation réactive plutôt que la programmation impérative
- Arrêter de penser en termes d’actions mais plutôt en termes de streams/flux
Utiliser des fonctions « pures »
Une fonction est pure lorsque :
- Pour des mêmes paramètres en entrée, elle retourne toujours la même valeur
- Pas d'effets de bord : elle ne fait subir aucune mutation aux objets
Hot vs Cold Observables
Cold
Un observable est « cold » (froid) lorsque les données sont générées à l'intérieur de l'observable
(exemple: un appel HTTP, pas de données si pas de subscribe).
Hot
Un observable est « hot » (chaud) lorsque les données sont générées en dehors de l'observable
(exemple: un évènement DOM, produit des valeurs même s'il n'est pas appelé).
Clean code
- Aligner les opérations les unes en dessous des autres :
foo$.pipe( map(...), filter(...), tap(...) )
- Extraire en différents streams en cas de complexité
- Extraire les opérations complexes dans des fonctions privées
- Eviter l'utilisation d'accolades si la fonctionnalité tient sur une ligne (arrow function)

