Vos Observables sont très puissants : du coup, ils peuvent également être dangereux. Si votre application maintient une souscription à un Observable qui ne sert plus, vous générez une fuite de mémoire. En plus, ces fuites deviennent vite nombreuses et peuvent créer de gros problèmes de performance, même jusqu'à faire planter le navigateur !
Du coup, il vous faut absolument implémenter une stratégie pour unsubscribe (se désabonner) des Observables auxquels vous avez souscrit. Dans ce chapitre, vous découvrirez les deux stratégies qui permettent d'empêcher toute fuite de mémoire potentielle causée par un Observable.
Trouvez la fuite
Vous allez maintenant créer une fuite de mémoire (mineure, ne vous inquiétez pas !) pour voir comment ces fuites peuvent vite devenir problématiques.
Dans face-snap-list.component.ts
, ajoutez cette ligne à ngOnInit()
:
interval(1000).pipe(tap(console.log)).subscribe();
Dans l'application, allez sur la liste de FaceSnaps, et dans la console vous verrez les nombres qui augmentent. Jusqu'ici, rien d'étonnant.
Mais qu'est-ce qui se passe si vous cliquez sur le bouton View
d'un FaceSnap et si vous revenez sur la liste ? Et si vous le faites plusieurs fois ?
À chaque fois que le ngOnInit()
de FaceSnapsListComponent est appelé, une nouvelle instance de l'Observable est créée ! Imaginez ce que ça pourrait donner sur toute une application, avec plusieurs Observables, s'ils émettent des objets ou des tableaux… ça devient vite problématique !
Désabonnez-vous
Si vous appelez la méthode subscribe()
d'un Observable dans votre code TypeScript, la meilleure façon d'éviter des fuites de mémoire est de vous assurer que cet Observable complète, car un Observable qui complète n'existe plus et ne peut créer de fuite.
Bien sûr, il faut uniquement que l'Observable complète si on n'en a plus besoin. Il y a deux cas principaux :
Vous savez en avance combien d'émissions vous intéressent – souvent, seulement la première émission nous intéresse, ou les quelques premières.
Vous avez besoin des émissions de l'Observable pendant toute la durée de vie du component.
Dans ces deux cas, vous allez utiliser des opérateurs spécifiques pour assurer la complétion, et donc la destruction, de l'Observable.
Prenez une ou plusieurs valeurs
Le cas le plus simple est quand vous savez combien d'émissions vous intéressent. Souvent, vous voulez uniquement la première émission d'un Observable. À partir du moment où le nombre d'émissions utiles est connu, vous utiliserez l'opérateur take()
.
take()
prend un nombre comme argument, et complète l'Observable quand il a émis ce nombre de valeurs. Ajoutons take(3)
, par exemple, à l'Observable qui fuit ci-dessus :
interval(1000).pipe(
take(3),
tap(console.log),
).subscribe();
Si vous regardez dans la console :
Et voilà ! Votre Observable complète au bout de 3 émissions, et ne fuit plus !
Détruisez l'Observable en détruisant le component
L'autre cas de figure très courant de souscription TypeScript est quand vous voulez recevoir toutes les émissions d'un Observable pendant la durée de vie du component. Du coup, il faut trouver un moyen de compléter l'Observable au moment de la destruction du component. Pour cela, vous allez utiliser le pattern "Destroy Subject".
Voici les différentes étapes de l'implémentation :
ngOnDestroy
Jusqu'ici, vous avez utilisé la méthode ngOnInit()
qui est appelée au moment de la création d'un component. ngOnInit()
est ce qu'on appelle un lifecycle hook : une méthode qui est appelée à un moment spécifique du cycle de vie de son component. Il existe plusieurs lifecycle hooks, mais celui qui va nous intéresser pour le pattern Destroy Subject est ngOnDestroy
.
ngOnDestroy
est appelé au moment de la destruction du component. Pour l'utiliser, il faut rajouter OnDestroy
(importé depuis @angular/core
) aux implements
du component :
export class FaceSnapListComponent implements OnInit, OnDestroy {
//...
Comme ça, vous pouvez ajouter la méthode ngOnDestroy()
dans votre component. La norme est de l'ajouter à la fin de la classe :
ngOnDestroy(): void {}
Subject
Vous allez créer un type spécial d'Observable : un Subject.
Un Subject est un Observable que vous pouvez faire émettre à la demande. Vous allez donc créer un Subject appelé destroy$
qui émettra une seule fois, au moment de la destruction du component.
Commencez par le déclarer en haut de votre classe :
export class FaceSnapListComponent implements OnInit, OnDestroy {
faceSnaps!: FaceSnap[];
private destroy$!: Subject<boolean>;
constructor(private faceSnapsService: FaceSnapsService) { }
//...
Il faut ensuite initialiser destroy$
dans ngOnInit()
:
ngOnInit(): void {
this.destroy$ = new Subject<boolean>();
//...
La dernière étape de la création de destroy$
est de le faire émettre dans ngOnDestroy()
. Pour faire émettre un Subject, on appelle sa méthode next()
:
ngOnDestroy(): void {
this.destroy$.next(true);
}
Avec ça, votre Subject est prêt : vous allez maintenant dire à votre Observable qui fuit de compléter lorsque destroy$
émettra – au moment de la destruction du component, donc !
takeUntil
La dernière étape du pattern Destroy Subject est d'ajouter l'opérateur takeUntil()
à votre Observable :
interval(1000).pipe(
tap(console.log),
takeUntil(this.destroy$)
).subscribe();
Cet opérateur dit à l'Observable interval
de continuer à émettre tant que destroy$
n'a pas émis, mais dès que destroy$
émet, de compléter l'Observable.
Faites l'essai dans votre application : allez sur la liste de FaceSnaps, attendez un peu et changez de route. Vous verrez l'Observable se compléter. Quand vous revenez à la liste, une nouvelle instance de l'Observable est créée. Voici le résultat après quelques navigations :
Les opérateurs take()
et takeUntil()
sont donc les deux à utiliser lorsque vous souscrivez à un Observable dans votre code TypeScript – lorsque vous appelez sa méthode subscribe()
.
Mais comme vous l'avez vu, ce n'est pas la seule façon de souscrire à un Observable !
Consommez sans modération avec le pipe async
Vous avez vu dans un chapitre précédent que vous pouvez souscrire à un Observable et afficher ses émissions dans le DOM avec le pipe async
.
Eh bien j'ai une bonne nouvelle pour vous ! Tout Observable souscrit avec le pipe async
est automatiquement unsubscribe lors de la destruction du component qui le consomme !
Vous n'avez donc pas à vous inquiéter des fuites de mémoire avec les Observables souscrits avec le pipe async
! La conséquence de ce comportement est que seuls les Observables souscrits avec la méthode subscribe()
nécessitent une stratégie spécifique de unsubscribe.
En résumé
Un Observable souscrit qui ne complète pas risque de créer des fuites de mémoire.
Si un Observable est souscrit avec la méthode
subscribe()
, 2 possibilités :Si vous connaissez le nombre d'émissions qui vous intéressent, utilisez l'opérateur
take()
.Si vous avez besoin de toutes les émissions durant la vie du component, utilisez le pattern Destroy Subject.
ngOnDestroy
est un lifecycle hook qui est appelé lors de la destruction du component.Un Subject est un Observable que l'on peut forcer à émettre avec sa méthode
next()
.Les Observables souscrits avec le pipe
async
sont unsubscribe automatiquement par Angular lors de la destruction du component.
Qu'avons-nous appris dans cette partie du cours ?
Félicitations ! Vous avez terminé la première partie de ce cours intermédiaire sur Angular au sujet des Observables. Voici ce que vous avez appris :
Vous avez découvert les Observables – ces flux de valeurs émis dans le temps – et vous y avez souscrit avec la méthode
subscribe()
et le pipeasync
;Vous avez utilisé des opérateurs bas niveau pour modifier, transformer, filtrer, et ajouter des effets secondaires avec les émissions des Observables ;
Vous avez appris les différences entre les opérateurs haut niveau – qui permettent à un Observable extérieur de souscrire à des Observables intérieurs – selon la façon dont ils traitent les émissions extérieures lorsqu'une souscription d'Observable intérieur n'a pas encore complété ;
Vous avez sécurisé votre application contre les fuites de mémoire avec les stratégies de
unsubscribe
–take
,takeUntil
, et le pipeasync
.
Les Observables sont un sujet complexe, et ils ont été abordés ici d'une manière plutôt théorique. Vous découvrirez, au cours des parties suivantes, certains des Observables intégrés dans le framework Angular, et j'espère que cette première partie difficile vous aidera à mieux les apprivoiser, et vous servira de référence à l'avenir tout au long de votre carrière de développeur !
Il est temps pour votre premier quiz. Validez ce que vous avez appris de cette première partie avant d'avancer à la partie 2 – à très vite !