• 8 hours
  • Medium

Free online content available in this course.

course.header.alt.is_video

course.header.alt.is_certifying

Got it!

Last updated on 3/31/22

Évitez les fuites de mémoire avec des stratégies de unsubscribe

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 ?

Il y a visiblement plusieurs instances de l'Observable en parallèle !
Observables partout…

À 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 :

On voit 0, puis 1, puis 2, puis … plus rien !
L'Observable complète après trois émissions

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 :

On voit qu'il n'y a jamais plus d'une instance de l'Observable, et que celui-ci complète dès qu'on détruit le component.
L'Observable complète lors de la destruction du component

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éthodenext()  .

  • 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 pipe  async  ;

  • 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 pipe  async  .

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 ! 

Example of certificate of achievement
Example of certificate of achievement