Angular / RxJs Quand dois-je me désinscrire de `Subscription`

720

Quand dois-je stocker les Subscriptioninstances et les invoquer unsubscribe()pendant le cycle de vie NgOnDestroy et quand puis-je simplement les ignorer?

L'enregistrement de tous les abonnements introduit beaucoup de désordre dans le code des composants.

Le Guide du client HTTP ignore les abonnements comme celui-ci:

getHeroes() {
  this.heroService.getHeroes()
                   .subscribe(
                     heroes => this.heroes = heroes,
                     error =>  this.errorMessage = <any>error);
}

Dans le même temps, Route & Navigation Guide dit que:

Finalement, nous naviguerons ailleurs. Le routeur supprimera ce composant du DOM et le détruira. Nous devons nettoyer après nous-mêmes avant que cela se produise. Plus précisément, nous devons nous désinscrire avant que Angular ne détruise le composant. Ne pas le faire pourrait créer une fuite de mémoire.

Nous nous désinscrivons de notre Observabledans la ngOnDestroyméthode.

private sub: any;

ngOnInit() {
  this.sub = this.route.params.subscribe(params => {
     let id = +params['id']; // (+) converts string 'id' to a number
     this.service.getHero(id).then(hero => this.hero = hero);
   });
}

ngOnDestroy() {
  this.sub.unsubscribe();
}
Sergey Tihon
la source
21
Je suppose que Subscriptions http-requestspeut être ignoré, car ils n'appellent onNextqu'une seule fois puis ils appellent onComplete. La Routerplace appelle à onNextplusieurs reprises et pourrait ne jamais appeler onComplete(pas sûr à ce sujet ...). Il en va de même pour Observables de Events. Donc je suppose que ça devrait l'être unsubscribed.
Springrbua
1
@ gt6707a Le flux se termine (ou ne se termine pas) indépendamment de toute observation de cette réalisation. Les rappels (l'observateur) fournis à la fonction d'abonnement ne déterminent pas si les ressources sont allouées. C'est l'appel à subscribelui-même qui alloue potentiellement des ressources en amont.
seangwright

Réponses:

981

--- Edit 4 - Ressources supplémentaires (2018/09/01)

Dans un récent épisode d' Aventures in Angular, Ben Lesh et Ward Bell discutent des questions concernant comment / quand se désinscrire dans un composant. La discussion commence vers 13 h 05:30.

Ward mentionne right now there's an awful takeUntil dance that takes a lot of machineryet Shai Reznik mentionne Angular handles some of the subscriptions like http and routing.

En réponse, Ben mentionne qu'il y a des discussions en ce moment pour permettre aux observables de se connecter aux événements du cycle de vie du composant angulaire et Ward suggère un observable des événements du cycle de vie auquel un composant pourrait souscrire pour savoir quand terminer les observables maintenus en tant qu'état interne du composant.

Cela dit, nous avons surtout besoin de solutions maintenant, voici donc d'autres ressources.

  1. Une recommandation pour le takeUntil()modèle du membre de l'équipe principale de RxJs Nicholas Jamieson et une règle tslint pour aider à l'appliquer. https://ncjamieson.com/avoiding-takeuntil-leaks/

  2. Package npm léger qui expose un opérateur Observable qui prend une instance de composant ( this) comme paramètre et se désabonne automatiquement pendant ngOnDestroy. https://github.com/NetanelBasal/ngx-take-until-destroy

  3. Une autre variante de ce qui précède avec une ergonomie légèrement meilleure si vous ne faites pas de builds AOT (mais nous devrions tous le faire maintenant). https://github.com/smnbbrv/ngx-rx-collector

  4. Directive personnalisée *ngSubscribequi fonctionne comme un canal asynchrone mais crée une vue intégrée dans votre modèle afin que vous puissiez vous référer à la valeur «non encapsulée» dans votre modèle. https://netbasal.com/diy-subscription-handling-directive-in-angular-c8f6e762697f

Je mentionne dans un commentaire au blog de Nicholas que la surutilisation de takeUntil()pourrait être un signe que votre composant essaie d'en faire trop et que la séparation de vos composants existants en composants de fonctionnalité et de présentation devrait être envisagée. Vous pouvez ensuite | asyncl'Observable à partir du composant Feature dans un Inputcomposant Presentational, ce qui signifie qu'aucun abonnement n'est nécessaire n'importe où. En savoir plus sur cette approche ici

--- Edit 3 - La solution «officielle» (2017/04/09)

J'ai parlé à Ward Bell de cette question à NGConf (je lui ai même montré cette réponse qu'il a dite correcte) mais il m'a dit que l'équipe de documentation pour Angular avait une solution à cette question qui n'est pas publiée (bien qu'ils travaillent à la faire approuver) ). Il m'a également dit que je pouvais mettre à jour ma réponse SO avec la prochaine recommandation officielle.

La solution que nous devrions tous utiliser à l'avenir est d'ajouter un private ngUnsubscribe = new Subject();champ à tous les composants qui ont des .subscribe()appels à Observables dans leur code de classe.

Nous appelons ensuite this.ngUnsubscribe.next(); this.ngUnsubscribe.complete();nos ngOnDestroy()méthodes.

La sauce secrète (comme déjà noté par @metamaker ) est d'appeler takeUntil(this.ngUnsubscribe)avant chacun de nos .subscribe()appels qui garantira que tous les abonnements seront nettoyés lorsque le composant sera détruit.

Exemple:

import { Component, OnDestroy, OnInit } from '@angular/core';
// RxJs 6.x+ import paths
import { filter, startWith, takeUntil } from 'rxjs/operators';
import { Subject } from 'rxjs';
import { BookService } from '../books.service';

@Component({
    selector: 'app-books',
    templateUrl: './books.component.html'
})
export class BooksComponent implements OnDestroy, OnInit {
    private ngUnsubscribe = new Subject();

    constructor(private booksService: BookService) { }

    ngOnInit() {
        this.booksService.getBooks()
            .pipe(
               startWith([]),
               filter(books => books.length > 0),
               takeUntil(this.ngUnsubscribe)
            )
            .subscribe(books => console.log(books));

        this.booksService.getArchivedBooks()
            .pipe(takeUntil(this.ngUnsubscribe))
            .subscribe(archivedBooks => console.log(archivedBooks));
    }

    ngOnDestroy() {
        this.ngUnsubscribe.next();
        this.ngUnsubscribe.complete();
    }
}

Remarque: Il est important d'ajouter l' takeUntilopérateur comme dernier pour éviter les fuites avec des observables intermédiaires dans la chaîne de l'opérateur.

--- Modifier 2 (28/12/2016)

Source 5

Le tutoriel Angular, le chapitre Routage indique maintenant ce qui suit: "Le routeur gère les observables qu'il fournit et localise les abonnements. Les abonnements sont nettoyés lorsque le composant est détruit, protégeant contre les fuites de mémoire, nous n'avons donc pas besoin de vous désabonner de la route params Observable. " - Mark Rajcok

Voici une discussion sur les problèmes de Github pour les documents Angular concernant Router Observables où Ward Bell mentionne qu'une clarification pour tout cela est en cours.

--- Modifier 1

Source 4

Dans cette vidéo de NgEurope, Rob Wormald dit également que vous n'avez pas besoin de vous désabonner de Router Observables. Il mentionne également le httpservice et ActivatedRoute.paramsdans cette vidéo de novembre 2016 .

--- Réponse originale

TLDR:

Pour cette question, il existe (2) sortes de Observables- valeur finie et valeur infinie .

http Observablesproduire des valeurs finies (1) et quelque chose comme un DOM event listener Observablesproduire des valeurs infinies .

Si vous appelez manuellement subscribe(sans utiliser de canal asynchrone), alors unsubscribedepuis l' infini Observables .

Ne vous inquiétez pas des finis , RxJsprenez soin d'eux.

Source 1

J'ai retrouvé une réponse de Rob Wormald dans Angular's Gitter ici .

Il déclare (j'ai réorganisé pour plus de clarté et l'accent est à moi)

s'il s'agit d' une séquence à valeur unique (comme une requête http), le nettoyage manuel n'est pas nécessaire (en supposant que vous vous abonnez manuellement dans le contrôleur)

je devrais dire "si c'est une séquence qui se termine " (dont les séquences à valeur unique, à la http, en sont une)

si c'est une séquence infinie , vous devez vous désabonner de ce que le canal asynchrone fait pour vous

Il mentionne également dans cette vidéo YouTube sur Observables que they clean up after themselves... dans le contexte d'Observables qui complete(comme Promises, qui se terminent toujours parce qu'ils produisent toujours 1 valeur et se terminant - nous ne nous sommes jamais inquiétés de la désinscription de Promises pour nous assurer qu'ils nettoient l' xhrévénement auditeurs, non?).

Source 2

Également dans le guide Rangle d'Angular 2, il lit

Dans la plupart des cas, nous n'aurons pas besoin d'appeler explicitement la méthode de désabonnement, sauf si nous voulons annuler tôt ou que notre observable a une durée de vie plus longue que notre abonnement. Le comportement par défaut des opérateurs observables consiste à supprimer l'abonnement dès que les messages .complete () ou .error () sont publiés. Gardez à l'esprit que le RxJS a été conçu pour être utilisé la plupart du temps de façon "feu et oubliez".

Quand la phrase our Observable has a longer lifespan than our subscriptions'applique-t-elle?

Elle s'applique lorsqu'un abonnement est créé à l'intérieur d'un composant qui est détruit avant (ou pas «longtemps» avant) la Observablefin.

Je lis cela comme signifiant que si nous souscrivons à une httpdemande ou à un observable qui émet 10 valeurs et que notre composant est détruit avant que cette httpdemande ne revienne ou que les 10 valeurs aient été émises, nous sommes toujours d'accord!

Lorsque la demande revient ou que la 10e valeur est finalement émise, la tâche Observablese termine et toutes les ressources sont nettoyées.

Source 3

Si nous regardons cet exemple dans le même guide Rangle, nous pouvons voir que le Subscriptionto route.paramsnécessite un unsubscribe()car nous ne savons pas quand ceux- paramsci cesseront de changer (en émettant de nouvelles valeurs).

Le composant pourrait être détruit en s'éloignant, auquel cas les paramètres d'itinéraire continueront probablement de changer (ils pourraient techniquement changer jusqu'à la fin de l'application) et les ressources allouées en abonnement seraient toujours allouées car il n'y en a pas eu completion.

seangwright
la source
16
L'appel complete()en lui-même ne semble pas nettoyer les abonnements. Quelle que soit la manière d'appeler next()et ensuite complete(), je crois takeUntil()que s'arrête uniquement lorsqu'une valeur est produite, et non lorsque la séquence est terminée.
Firefly
3
@seangwright Un test rapide avec un membre de type à l' Subjectintérieur d'un composant et en le basculant ngIfpour déclencher ngOnInitet ngOnDestroymontre que le sujet et ses abonnements ne se termineront jamais ou ne seront jamais supprimés (connecté un finallyopérateur à l'abonnement). Je dois appeler Subject.complete()à ngOnDestroy, de sorte que les abonnements peuvent nettoyer après eux - mêmes.
Lars
4
Votre --- Edit 3 est très perspicace, merci! J'ai juste une question de suivi: si vous utilisez l' takeUnitlapproche, nous ne devons jamais nous désabonner manuellement des observables? Est-ce le cas? De plus, pourquoi devons-nous appeler next()le ngOnDestroy, pourquoi ne pas simplement appeler complete()?
uglycode
7
@seangwright C'est décevant; le passe-partout supplémentaire est ennuyeux.
spongessuck
4
Edit 3 discuté dans le contexte des événements sur medium.com/@benlesh/rxjs-dont-unsubscribe-6753ed4fda87
HankCa
91

Vous n'avez pas besoin d'avoir un tas d'abonnements et de vous désabonner manuellement. Utilisez Subject et takeUntil combo pour gérer les abonnements comme un boss:

import { Subject } from "rxjs"
import { takeUntil } from "rxjs/operators"

@Component({
  moduleId: __moduleName,
  selector: "my-view",
  templateUrl: "../views/view-route.view.html"
})
export class ViewRouteComponent implements OnInit, OnDestroy {
  componentDestroyed$: Subject<boolean> = new Subject()

  constructor(private titleService: TitleService) {}

  ngOnInit() {
    this.titleService.emitter1$
      .pipe(takeUntil(this.componentDestroyed$))
      .subscribe((data: any) => { /* ... do something 1 */ })

    this.titleService.emitter2$
      .pipe(takeUntil(this.componentDestroyed$))
      .subscribe((data: any) => { /* ... do something 2 */ })

    //...

    this.titleService.emitterN$
      .pipe(takeUntil(this.componentDestroyed$))
      .subscribe((data: any) => { /* ... do something N */ })
  }

  ngOnDestroy() {
    this.componentDestroyed$.next(true)
    this.componentDestroyed$.complete()
  }
}

Une approche alternative , proposée par @acumartini dans les commentaires , utilise takeWhile au lieu de takeUntil . Vous pouvez le préférer, mais sachez que de cette façon, votre exécution observable ne sera pas annulée lors de la suppression de ng de votre composant (par exemple lorsque vous effectuez des calculs longs ou attendez des données du serveur). La méthode, qui est basée sur takeUntil , n'a pas cet inconvénient et conduit à l'annulation immédiate de la demande. Merci à @AlexChe pour une explication détaillée dans les commentaires .

Voici donc le code:

@Component({
  moduleId: __moduleName,
  selector: "my-view",
  templateUrl: "../views/view-route.view.html"
})
export class ViewRouteComponent implements OnInit, OnDestroy {
  alive: boolean = true

  constructor(private titleService: TitleService) {}

  ngOnInit() {
    this.titleService.emitter1$
      .pipe(takeWhile(() => this.alive))
      .subscribe((data: any) => { /* ... do something 1 */ })

    this.titleService.emitter2$
      .pipe(takeWhile(() => this.alive))
      .subscribe((data: any) => { /* ... do something 2 */ })

    // ...

    this.titleService.emitterN$
      .pipe(takeWhile(() => this.alive))
      .subscribe((data: any) => { /* ... do something N */ })
  }

  // Probably, this.alive = false MAY not be required here, because
  // if this.alive === undefined, takeWhile will stop. I
  // will check it as soon, as I have time.
  ngOnDestroy() {
    this.alive = false
  }
}
métamaker
la source
2
S'il utilise juste un bool pour garder l'état, comment faire fonctionner "takeUntil" comme prévu?
Val
6
Je pense qu'il y a une différence significative entre l'utilisation de takeUntilet takeWhile. Le premier se désabonne de la source observable immédiatement lorsqu'il est renvoyé, tandis que le dernier se désabonne uniquement dès que la valeur suivante est produite par la source observable. Si la production d'une valeur par la source observable est une opération consommatrice de ressources, le choix entre les deux peut aller au-delà de la préférence de style. Voir le plunk
Alex Che
2
@AlexChe merci d'avoir fourni un plunk intéressant! C'est un point très valable pour une utilisation générale de takeUntilvs takeWhile, cependant, pas pour notre cas spécifique. Lorsque nous devons désinscrire des écouteurs lors de la destruction de composants , nous vérifions simplement la valeur booléenne comme () => alivedans takeWhile, donc aucune opération consommatrice de temps / mémoire n'est utilisée et la différence concerne à peu près le style (ofc, dans ce cas spécifique).
metamaker
2
@metamaker Say, dans notre composant, nous souscrivons à un Observable, qui extrait en interne de la crypto-monnaie et déclenche un nextévénement pour chaque pièce extraite, et l'extraction d'une de ces pièces prend une journée. Avec takeUntilnous nous désabonnerons de l'exploitation minière source Observableimmédiatement une fois ngOnDestroyappelé lors de la destruction de nos composants. Ainsi, la Observablefonction d' exploration de données est en mesure d'annuler son fonctionnement immédiatement au cours de ce processus.
Alex Che
2
OTOH, si nous utilisons takeWhile, dans le ngOnDestorynous définissons simplement la variable booléenne. Mais la Observablefonction d' exploration peut encore fonctionner jusqu'à un jour, et ce n'est qu'à ce moment-là qu'elle nextse rendra compte qu'il n'y a pas d'abonnement actif et qu'elle doit annuler.
Alex Che
76

La classe d'abonnement a une fonctionnalité intéressante:

Représente une ressource jetable, telle que l'exécution d'un observable. Un abonnement a une méthode importante, le désabonnement, qui ne prend aucun argument et supprime simplement la ressource détenue par l'abonnement.
De plus, les abonnements peuvent être regroupés via la méthode add (), qui attachera un abonnement enfant à l'abonnement actuel. Lorsqu'un abonnement est désabonné, tous ses enfants (et ses petits-enfants) seront également désabonnés.

Vous pouvez créer un objet Subscription agrégé qui regroupe tous vos abonnements. Pour ce faire, créez un abonnement vide et ajoutez-y des abonnements à l'aide de sa add()méthode. Lorsque votre composant est détruit, il vous suffit de résilier l'abonnement global.

@Component({ ... })
export class SmartComponent implements OnInit, OnDestroy {
  private subscriptions = new Subscription();

  constructor(private heroService: HeroService) {
  }

  ngOnInit() {
    this.subscriptions.add(this.heroService.getHeroes().subscribe(heroes => this.heroes = heroes));
    this.subscriptions.add(/* another subscription */);
    this.subscriptions.add(/* and another subscription */);
    this.subscriptions.add(/* and so on */);
  }

  ngOnDestroy() {
    this.subscriptions.unsubscribe();
  }
}
Steven Liekens
la source
1
J'utilise cette approche. Vous vous demandez si c'est mieux que d'utiliser l'approche avec takeUntil (), comme dans la réponse acceptée .. inconvénients?
Manuel Di Iorio
1
Pas d'inconvénients à ma connaissance. Je ne pense pas que ce soit mieux, juste différent.
Steven Liekens
3
Voir medium.com/@benlesh/rxjs-dont-unsubscribe-6753ed4fda87 pour une discussion plus approfondie sur l' takeUntilapproche officielle par rapport à cette approche de collecte des abonnements et d'appel unsubscribe. (Cette approche me semble beaucoup plus propre.)
Josh Kelley
4
Un petit avantage de cette réponse: vous n'avez pas à vérifier si elle this.subscriptionsest nulle
user2023861
2
Évitez simplement le chaînage de méthodes d'ajout comme, sub = subsciption.add(..).add(..)car dans de nombreux cas, cela produit des résultats inattendus github.com/ReactiveX/rxjs/issues/2769#issuecomment-345636477
Evgeniy Generalov
32

Certaines des meilleures pratiques concernant les désabonnements observables dans les composants Angular:

Une citation de Routing & Navigation

Lorsque vous vous abonnez à un élément observable dans un composant, vous vous arrêtez presque toujours pour vous désinscrire lorsque le composant est détruit.

Il y a quelques observables exceptionnels où cela n'est pas nécessaire. Les observables ActivatedRoute font partie des exceptions.

L'ActivatedRoute et ses observables sont isolés du routeur lui-même. Le routeur détruit un composant routé lorsqu'il n'est plus nécessaire et le ActivatedRoute injecté meurt avec lui.

N'hésitez pas à vous désinscrire de toute façon. C'est inoffensif et jamais une mauvaise pratique.

Et en répondant aux liens suivants:

J'ai rassemblé certaines des meilleures pratiques concernant les désabonnements observables dans les composants Angular à partager avec vous:

  • httple désabonnement observable est conditionnel et nous devons considérer les effets du «rappel d'abonnement» exécuté après la destruction du composant au cas par cas. On sait que angulaire se désabonne et nettoie l' httpobservable lui-même (1) , (2) . Bien que cela soit vrai du point de vue des ressources, cela ne raconte que la moitié de l'histoire. Disons que nous parlons d'appeler directement à httppartir d'un composant, ethttp réponse a pris plus de temps que nécessaire, donc l'utilisateur a fermé le composant. lesubscribe()Le gestionnaire sera toujours appelé même si le composant est fermé et détruit. Cela peut avoir des effets secondaires indésirables et, dans les pires scénarios, laisser l'état de l'application cassé. Il peut également provoquer des exceptions si le code du rappel tente d'appeler quelque chose qui vient d'être supprimé. Cependant, en même temps, ils sont parfois souhaités. Disons que vous créez un client de messagerie et que vous déclenchez un son lorsque l'envoi de l'e-mail est terminé - vous voudriez quand même que cela se produise même si le composant est fermé ( 8 ).
  • Pas besoin de vous désabonner des observables qui se terminent ou qui font une erreur. Cependant, cela ne fait aucun mal (7) .
  • Utilisation AsyncPipe autant que possible car il se désabonne automatiquement de l'observable lors de la destruction des composants.
  • Désabonnez-vous des ActivatedRouteobservables comme route.paramss'ils étaient abonnés à l'intérieur d'un composant imbriqué (ajouté à l'intérieur de tpl avec le sélecteur de composant) ou dynamique car ils peuvent être abonnés plusieurs fois tant que le composant parent / hôte existe. Pas besoin de vous désinscrire d'eux dans d'autres scénarios comme mentionné dans la citation ci-dessus de la Routing & Navigationdocumentation.
  • Désabonnez-vous des observables globaux partagés entre les composants qui sont exposés via un service Angular, par exemple, car ils peuvent être abonnés plusieurs fois tant que le composant est initialisé.
  • Pas besoin de vous désabonner des observables internes d'un service à portée d'application car ce service n'est jamais détruit, à moins que votre application entière ne soit détruite, il n'y a aucune raison réelle de vous désabonner et il n'y a aucun risque de fuite de mémoire. (6) .

    Remarque: En ce qui concerne les services étendus, c'est-à-dire les fournisseurs de composants, ils sont détruits lorsque le composant est détruit. Dans ce cas, si nous nous abonnons à tout observable à l'intérieur de ce fournisseur, nous devrions envisager de nous désinscrire de celui-ci en utilisant le OnDestroyhook de cycle de vie qui sera appelé lorsque le service sera détruit, selon les documents.
  • Utilisez une technique abstraite pour éviter tout désordre de code pouvant résulter de désabonnements. Vous pouvez gérer vos abonnements avec takeUntil (3) ou vous pouvez utiliser ce npm package mentionné à (4) La façon la plus simple de vous désabonner d'Observables dans Angular .
  • Désabonnez-vous toujours des FormGroupobservables comme form.valueChangesetform.statusChanges
  • Désabonnez-vous toujours des observables de Renderer2service commerenderer2.listen
  • Désabonnez-vous de tout autre élément observable en tant qu'étape de protection contre les fuites de mémoire jusqu'à ce que Angular Docs nous indique explicitement quels observables ne sont pas nécessaires pour être désabonné (vérifiez le problème: (5) Documentation pour la désinscription RxJS (ouverte) ).
  • Bonus: utilisez toujours les méthodes angulaires pour lier des événements, HostListenercar angular se soucie bien de la suppression des écouteurs d'événements si nécessaire et empêche toute fuite de mémoire potentielle due aux liaisons d'événements.

Un bon dernier conseil : si vous ne savez pas si un observable est automatiquement désabonné / terminé ou non, ajoutez un completerappel à subscribe(...)et vérifiez s'il est appelé lorsque le composant est détruit.

Mouneer
la source
La réponse pour le n ° 6 n'est pas tout à fait juste. Les services sont détruits et leur ngOnDestroyest appelé lorsque le service est fourni à un niveau autre que le niveau racine, par exemple fourni explicitement dans un composant qui est supprimé ultérieurement. Dans ces cas, vous devez vous désinscrire des services observables internes
Drenai
@Drenai, merci pour votre commentaire et poliment je ne suis pas d'accord. Si un composant est détruit, le composant, le service et l'observable seront tous GCed et la désinscription sera inutile dans ce cas sauf si vous gardez une référence pour l'observable n'importe où loin du composant (ce qui n'est pas logique de divulguer les états du composant globalement malgré l'étendue du service sur le composant)
Mouneer
Si le service en cours de destruction a un abonnement à un observable appartenant à un autre service plus haut dans la hiérarchie DI, le GC ne se produira pas. Éviter ce scénario par désabonnement dans ngOnDestroy, ce qui est toujours appelé lorsque les services sont détruits github.com/angular/angular/commit/...
Drenai
1
@Drenai, Vérifiez la réponse mise à jour.
Mouneer
3
@Tim Tout d'abord, Feel free to unsubscribe anyway. It is harmless and never a bad practice.et concernant votre question, cela dépend. Si le composant enfant est lancé plusieurs fois (par exemple, ajouté à l'intérieur ngIfou chargé dynamiquement), vous devez vous désabonner pour éviter d'ajouter plusieurs abonnements au même observateur. Sinon, pas besoin. Mais je préfère me désinscrire à l'intérieur du composant enfant car cela le rend plus réutilisable et isolé de la façon dont il pourrait être utilisé.
Mouneer
18

Ça dépend. Si, en appelant someObservable.subscribe(), vous commencez à bloquer certaines ressources qui doivent être libérées manuellement lorsque le cycle de vie de votre composant est terminé, vous devez appeler theSubscription.unsubscribe()pour éviter une fuite de mémoire.

Examinons de plus près vos exemples:

getHero()renvoie le résultat de http.get(). Si vous examinez le code source angulaire 2 , http.get()crée deux écouteurs d'événements:

_xhr.addEventListener('load', onLoad);
_xhr.addEventListener('error', onError);

et en appelant unsubscribe(), vous pouvez annuler la demande ainsi que les auditeurs:

_xhr.removeEventListener('load', onLoad);
_xhr.removeEventListener('error', onError);
_xhr.abort();

Notez que cela _xhrest spécifique à la plate-forme, mais je pense qu'il est prudent de supposer qu'il s'agit d'unXMLHttpRequest() cas dans votre cas.

Normalement, c'est suffisamment de preuves pour justifier un manuel unsubscribe() appel . Mais selon cette spécification WHATWG , le XMLHttpRequest()est sujet au ramasse-miettes une fois qu'il est "terminé", même s'il y a des écouteurs d'événement qui lui sont attachés. Donc je suppose que c'est pourquoi le guide officiel angular 2 ometunsubscribe() et laisse GC nettoyer les auditeurs.

Quant à votre deuxième exemple, cela dépend de l'implémentation de params. À ce jour, le guide officiel angulaire ne montre plus de désabonnement params. J'ai regardé nouveau src et trouvé que paramsc'était juste un sujet de comportement . Puisqu'aucun écouteur ou temporisateur d'événement n'a été utilisé et qu'aucune variable globale n'a été créée, il devrait être sûr d'omettreunsubscribe() .

L'essentiel de votre question est de toujours appeler unsubscribe() une garde contre les fuites de mémoire, sauf si vous êtes certain que l'exécution de l'observable ne crée pas de variables globales, n'ajoute pas d'écouteurs d'événements, ne définit pas de temporisateur ou ne fait rien d'autre qui entraîne des fuites de mémoire .

En cas de doute, examinez la mise en œuvre de cet observable. Si l'observable a écrit une logique de nettoyage dans son unsubscribe(), qui est généralement la fonction renvoyée par le constructeur, alors vous avez de bonnes raisons d'envisager sérieusement d'appeler unsubscribe().

Chuanqi Sun
la source
6

La documentation officielle d'Angular 2 fournit une explication pour savoir quand se désinscrire et quand elle peut être ignorée en toute sécurité. Jetez un œil à ce lien:

https://angular.io/docs/ts/latest/cookbook/component-communication.html#!#bidirectional-service

Recherchez le paragraphe avec le titre Parent et enfants communiquent via un service , puis la case bleue:

Notez que nous capturons l'abonnement et nous nous désabonnons lorsque le AstronautComponent est détruit. Il s'agit d'une étape de protection contre les fuites de mémoire. Il n'y a pas de risque réel dans cette application car la durée de vie d'un AstronautComponent est la même que la durée de vie de l'application elle-même. Ce ne serait pas toujours vrai dans une application plus complexe.

Nous n'ajoutons pas cette garde au MissionControlComponent car, en tant que parent, il contrôle la durée de vie du MissionService.

J'espère que ceci vous aide.

Cerny
la source
3
en tant que composant, vous ne savez jamais si vous êtes un enfant ou non. par conséquent, vous devez toujours vous désabonner des abonnements en tant que meilleure pratique.
SeriousM
1
Le point sur MissionControlComponent n'est pas vraiment de savoir s'il s'agit d'un parent ou non, c'est que le composant lui-même fournit le service. Lorsque MissionControl est détruit, le service et les références à l'instance du service le font également, il n'y a donc aucune possibilité de fuite.
ender
6

Basé sur: Utilisation de l'héritage de classe pour se connecter au cycle de vie du composant Angular 2

Une autre approche générique:

export abstract class UnsubscribeOnDestroy implements OnDestroy {
  protected d$: Subject<any>;

  constructor() {
    this.d$ = new Subject<void>();

    const f = this.ngOnDestroy;
    this.ngOnDestroy = () => {
      f();
      this.d$.next();
      this.d$.complete();
    };
  }

  public ngOnDestroy() {
    // no-op
  }

}

Et utilise :

@Component({
    selector: 'my-comp',
    template: ``
})
export class RsvpFormSaveComponent extends UnsubscribeOnDestroy implements OnInit {

    constructor() {
        super();
    }

    ngOnInit(): void {
      Observable.of('bla')
      .takeUntil(this.d$)
      .subscribe(val => console.log(val));
    }
}

Faire du jogging
la source
1
Cela ne fonctionne PAS correctement. Soyez prudent lorsque vous utilisez cette solution. Vous this.componentDestroyed$.next()
manquez
4

La réponse officielle (et les variantes) de l'édition n ° 3 fonctionne bien, mais ce qui m'attire, c'est le brouillage de la logique métier autour de l'abonnement observable.

Voici une autre approche utilisant des wrappers.

Warining: code expérimental

Le fichier subscribeAndGuard.ts est utilisé pour créer une nouvelle extension observable à encapsuler .subscribe()et en son sein à encapsuler ngOnDestroy().
L'utilisation est la même que .subscribe(), sauf pour un premier paramètre supplémentaire référençant le composant.

import { Observable } from 'rxjs/Observable';
import { Subscription } from 'rxjs/Subscription';

const subscribeAndGuard = function(component, fnData, fnError = null, fnComplete = null) {

  // Define the subscription
  const sub: Subscription = this.subscribe(fnData, fnError, fnComplete);

  // Wrap component's onDestroy
  if (!component.ngOnDestroy) {
    throw new Error('To use subscribeAndGuard, the component must implement ngOnDestroy');
  }
  const saved_OnDestroy = component.ngOnDestroy;
  component.ngOnDestroy = () => {
    console.log('subscribeAndGuard.onDestroy');
    sub.unsubscribe();
    // Note: need to put original back in place
    // otherwise 'this' is undefined in component.ngOnDestroy
    component.ngOnDestroy = saved_OnDestroy;
    component.ngOnDestroy();

  };

  return sub;
};

// Create an Observable extension
Observable.prototype.subscribeAndGuard = subscribeAndGuard;

// Ref: https://www.typescriptlang.org/docs/handbook/declaration-merging.html
declare module 'rxjs/Observable' {
  interface Observable<T> {
    subscribeAndGuard: typeof subscribeAndGuard;
  }
}

Voici un composant avec deux abonnements, un avec le wrapper et un sans. La seule mise en garde est qu'il doit implémenter OnDestroy (avec un corps vide si vous le souhaitez), sinon Angular ne sait pas appeler la version encapsulée.

import { Component, OnInit, OnDestroy } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import 'rxjs/Rx';
import './subscribeAndGuard';

@Component({
  selector: 'app-subscribing',
  template: '<h3>Subscribing component is active</h3>',
})
export class SubscribingComponent implements OnInit, OnDestroy {

  ngOnInit() {

    // This subscription will be terminated after onDestroy
    Observable.interval(1000)
      .subscribeAndGuard(this,
        (data) => { console.log('Guarded:', data); },
        (error) => { },
        (/*completed*/) => { }
      );

    // This subscription will continue after onDestroy
    Observable.interval(1000)
      .subscribe(
        (data) => { console.log('Unguarded:', data); },
        (error) => { },
        (/*completed*/) => { }
      );
  }

  ngOnDestroy() {
    console.log('SubscribingComponent.OnDestroy');
  }
}

Un plongeur de démonstration est ici

Une note supplémentaire: Re Edit 3 - La solution «officielle», cela peut être simplifié en utilisant takeWhile () au lieu de takeUntil () avant les abonnements, et un simple booléen plutôt qu'un autre observable dans ngOnDestroy.

@Component({...})
export class SubscribingComponent implements OnInit, OnDestroy {

  iAmAlive = true;
  ngOnInit() {

    Observable.interval(1000)
      .takeWhile(() => { return this.iAmAlive; })
      .subscribe((data) => { console.log(data); });
  }

  ngOnDestroy() {
    this.iAmAlive = false;
  }
}
Richard Matsen
la source
3

Étant donné que la solution de seangwright (Édition 3) semble être très utile, j'ai également trouvé difficile d'incorporer cette fonctionnalité dans le composant de base, et je suggère aux autres coéquipiers du projet de se rappeler d'appeler super () sur ngOnDestroy pour activer cette fonctionnalité.

Cette réponse fournit un moyen de se libérer du super appel et de faire de "componentDestroyed $" un noyau du composant de base.

class BaseClass {
    protected componentDestroyed$: Subject<void> = new Subject<void>();
    constructor() {

        /// wrap the ngOnDestroy to be an Observable. and set free from calling super() on ngOnDestroy.
        let _$ = this.ngOnDestroy;
        this.ngOnDestroy = () => {
            this.componentDestroyed$.next();
            this.componentDestroyed$.complete();
            _$();
        }
    }

    /// placeholder of ngOnDestroy. no need to do super() call of extended class.
    ngOnDestroy() {}
}

Et puis vous pouvez utiliser cette fonctionnalité librement par exemple:

@Component({
    selector: 'my-thing',
    templateUrl: './my-thing.component.html'
})
export class MyThingComponent extends BaseClass implements OnInit, OnDestroy {
    constructor(
        private myThingService: MyThingService,
    ) { super(); }

    ngOnInit() {
        this.myThingService.getThings()
            .takeUntil(this.componentDestroyed$)
            .subscribe(things => console.log(things));
    }

    /// optional. not a requirement to implement OnDestroy
    ngOnDestroy() {
        console.log('everything works as intended with or without super call');
    }

}
Val
la source
3

Suite à la réponse de @seangwright , j'ai écrit une classe abstraite qui gère les abonnements aux observables "infinis" dans les composants:

import { OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs/Subscription';
import { Subject } from 'rxjs/Subject';
import { Observable } from 'rxjs/Observable';
import { PartialObserver } from 'rxjs/Observer';

export abstract class InfiniteSubscriberComponent implements OnDestroy {
  private onDestroySource: Subject<any> = new Subject();

  constructor() {}

  subscribe(observable: Observable<any>): Subscription;

  subscribe(
    observable: Observable<any>,
    observer: PartialObserver<any>
  ): Subscription;

  subscribe(
    observable: Observable<any>,
    next?: (value: any) => void,
    error?: (error: any) => void,
    complete?: () => void
  ): Subscription;

  subscribe(observable: Observable<any>, ...subscribeArgs): Subscription {
    return observable
      .takeUntil(this.onDestroySource)
      .subscribe(...subscribeArgs);
  }

  ngOnDestroy() {
    this.onDestroySource.next();
    this.onDestroySource.complete();
  }
}

Pour l'utiliser, il suffit de l'étendre dans votre composant angulaire et d'appeler la subscribe()méthode comme suit:

this.subscribe(someObservable, data => doSomething());

Il accepte également l'erreur et complète les rappels comme d'habitude, un objet observateur, ou pas les rappels du tout. N'oubliez pas d'appeler super.ngOnDestroy()si vous implémentez également cette méthode dans le composant enfant.

Trouvez ici une référence supplémentaire de Ben Lesh: RxJS: Don't Unsubscribe .

Mau Muñoz
la source
2

J'ai essayé la solution de seangwright (Edit 3)

Cela ne fonctionne pas pour Observable créé par minuterie ou intervalle.

Cependant, je l'ai fait fonctionner en utilisant une autre approche:

import { Component, OnDestroy, OnInit } from '@angular/core';
import 'rxjs/add/operator/takeUntil';
import { Subject } from 'rxjs/Subject';
import { Subscription } from 'rxjs/Subscription';
import 'rxjs/Rx';

import { MyThingService } from '../my-thing.service';

@Component({
   selector: 'my-thing',
   templateUrl: './my-thing.component.html'
})
export class MyThingComponent implements OnDestroy, OnInit {
   private subscriptions: Array<Subscription> = [];

  constructor(
     private myThingService: MyThingService,
   ) { }

  ngOnInit() {
    const newSubs = this.myThingService.getThings()
        .subscribe(things => console.log(things));
    this.subscriptions.push(newSubs);
  }

  ngOnDestroy() {
    for (const subs of this.subscriptions) {
      subs.unsubscribe();
   }
 }
}
Jeff Tham
la source
2

J'aime les deux dernières réponses, mais j'ai rencontré un problème si la sous-classe référencée "this"dansngOnDestroy .

Je l'ai modifié pour être ceci, et il semble qu'il ait résolu ce problème.

export abstract class BaseComponent implements OnDestroy {
    protected componentDestroyed$: Subject<boolean>;
    constructor() {
        this.componentDestroyed$ = new Subject<boolean>();
        let f = this.ngOnDestroy;
        this.ngOnDestroy = function()  {
            // without this I was getting an error if the subclass had
            // this.blah() in ngOnDestroy
            f.bind(this)();
            this.componentDestroyed$.next(true);
            this.componentDestroyed$.complete();
        };
    }
    /// placeholder of ngOnDestroy. no need to do super() call of extended class.
    ngOnDestroy() {}
}
Scott Williams
la source
vous devez utiliser la fonction flèche pour lier le 'ceci':this.ngOnDestroy = () => { f.bind(this)(); this.componentDestroyed$.complete(); };
Damsorian
2

Dans le cas où la désinscription est nécessaire, l'opérateur suivant pour la méthode de tuyau observable peut être utilisé

import { Observable, Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { OnDestroy } from '@angular/core';

export const takeUntilDestroyed = (componentInstance: OnDestroy) => <T>(observable: Observable<T>) => {
  const subjectPropertyName = '__takeUntilDestroySubject__';
  const originalOnDestroy = componentInstance.ngOnDestroy;
  const componentSubject = componentInstance[subjectPropertyName] as Subject<any> || new Subject();

  componentInstance.ngOnDestroy = (...args) => {
    originalOnDestroy.apply(componentInstance, args);
    componentSubject.next(true);
    componentSubject.complete();
  };

  return observable.pipe(takeUntil<T>(componentSubject));
};

il peut être utilisé comme ceci:

import { Component, OnDestroy, OnInit } from '@angular/core';
import { Observable } from 'rxjs';

@Component({ template: '<div></div>' })
export class SomeComponent implements OnInit, OnDestroy {

  ngOnInit(): void {
    const observable = Observable.create(observer => {
      observer.next('Hello');
    });

    observable
      .pipe(takeUntilDestroyed(this))
      .subscribe(val => console.log(val));
  }

  ngOnDestroy(): void {
  }
}

L'opérateur encapsule la méthode ngOnDestroy du composant.

Important: l'opérateur doit être le dernier dans un tuyau observable.

Oleg Polezky
la source
Cela a très bien fonctionné, mais la mise à niveau vers angular 9 semble le tuer. Quelqu'un sait pourquoi?
ymerej
1

Vous devez généralement vous désinscrire lorsque les composants sont détruits, mais Angular va le gérer de plus en plus au fur et à mesure, par exemple dans la nouvelle version mineure d'Angular4, ils ont cette section pour acheminer le désabonnement:

Devez-vous vous désinscrire?

Comme décrit dans ActivatedRoute: le guichet unique pour les informations sur l'itinéraire de la page Routage et navigation, le routeur gère les observables qu'il fournit et localise les abonnements. Les abonnements sont nettoyés lorsque le composant est détruit, protégeant contre les fuites de mémoire, vous n'avez donc pas besoin de vous désabonner de la route paramMap Observable.

De plus, l'exemple ci-dessous est un bon exemple d'Angular pour créer un composant et le détruire après, regardez comment le composant implémente OnDestroy, si vous avez besoin de onInit, vous pouvez également l'implémenter dans votre composant, comme implémente OnInit, OnDestroy

import { Component, Input, OnDestroy } from '@angular/core';  
import { MissionService } from './mission.service';
import { Subscription }   from 'rxjs/Subscription';

@Component({
  selector: 'my-astronaut',
  template: `
    <p>
      {{astronaut}}: <strong>{{mission}}</strong>
      <button
        (click)="confirm()"
        [disabled]="!announced || confirmed">
        Confirm
      </button>
    </p>
  `
})

export class AstronautComponent implements OnDestroy {
  @Input() astronaut: string;
  mission = '<no mission announced>';
  confirmed = false;
  announced = false;
  subscription: Subscription;

  constructor(private missionService: MissionService) {
    this.subscription = missionService.missionAnnounced$.subscribe(
      mission => {
        this.mission = mission;
        this.announced = true;
        this.confirmed = false;
    });
  }

  confirm() {
    this.confirmed = true;
    this.missionService.confirmMission(this.astronaut);
  }

  ngOnDestroy() {
    // prevent memory leak when component destroyed
    this.subscription.unsubscribe();
  }
}
Alireza
la source
3
Confus. Que dis-tu ici? Vous (Angular docs / notes récents) semblez dire qu'Angular s'en charge et ensuite confirmer que la désinscription est un bon modèle. Merci.
jamie
1

Un autre petit ajout aux situations mentionnées ci-dessus est:

  • Désabonnez-vous toujours, lorsque de nouvelles valeurs dans le flux souscrit ne sont plus nécessaires ou importent peu, cela entraînera une diminution du nombre de déclencheurs et une augmentation des performances dans quelques cas. Des cas tels que des composants où les données / événements souscrits n'existent plus ou un nouvel abonnement à un tout nouveau flux est requis (actualisation, etc.) est un bon exemple de désabonnement.
Krishna Ganeriwal
la source
0

dans l'application SPA à la fonction ngOnDestroy (cycle de vie angulaire) Pour chaque abonnement, vous devez vous désinscrire . avantage => pour éviter que l'état ne devienne trop lourd.

par exemple: dans le composant1:

import {UserService} from './user.service';

private user = {name: 'test', id: 1}

constructor(public userService: UserService) {
    this.userService.onUserChange.next(this.user);
}

en service:

import {BehaviorSubject} from 'rxjs/BehaviorSubject';

public onUserChange: BehaviorSubject<any> = new BehaviorSubject({});

dans le composant 2:

import {Subscription} from 'rxjs/Subscription';
import {UserService} from './user.service';

private onUserChange: Subscription;

constructor(public userService: UserService) {
    this.onUserChange = this.userService.onUserChange.subscribe(user => {
        console.log(user);
    });
}

public ngOnDestroy(): void {
    // note: Here you have to be sure to unsubscribe to the subscribe item!
    this.onUserChange.unsubscribe();
}
mojtaba ramezani
la source
0

Pour gérer l'abonnement, j'utilise une classe "Unsubscriber".

Voici la classe de désabonnement.

export class Unsubscriber implements OnDestroy {
  private subscriptions: Subscription[] = [];

  addSubscription(subscription: Subscription | Subscription[]) {
    if (Array.isArray(subscription)) {
      this.subscriptions.push(...subscription);
    } else {
      this.subscriptions.push(subscription);
    }
  }

  unsubscribe() {
    this.subscriptions
      .filter(subscription => subscription)
      .forEach(subscription => {
        subscription.unsubscribe();
      });
  }

  ngOnDestroy() {
    this.unsubscribe();
  }
}

Et vous pouvez utiliser cette classe dans n'importe quel composant / service / effet, etc.

Exemple:

class SampleComponent extends Unsubscriber {
    constructor () {
        super();
    }

    this.addSubscription(subscription);
}
Pratiyush
la source
0

Vous pouvez utiliser la dernière Subscriptionclasse pour vous désinscrire de l'Observable avec un code pas si compliqué.

Nous pouvons le faire avec, normal variablemais ce sera override the last subscriptionà chaque nouvel abonnement, alors évitez cela, et cette approche est très utile lorsque vous traitez avec plus de nombre d'Observables et de type d'Observables comme BehavoiurSubjectetSubject

Abonnement

Représente une ressource jetable, telle que l'exécution d'un observable. Un abonnement a une méthode importante, le désabonnement, qui ne prend aucun argument et supprime simplement la ressource détenue par l'abonnement.

vous pouvez l'utiliser de deux manières,

  • vous pouvez directement pousser l'abonnement à Subscription Array

     subscriptions:Subscription[] = [];
    
     ngOnInit(): void {
    
       this.subscription.push(this.dataService.getMessageTracker().subscribe((param: any) => {
                //...  
       }));
    
       this.subscription.push(this.dataService.getFileTracker().subscribe((param: any) => {
            //...
        }));
     }
    
     ngOnDestroy(){
        // prevent memory leak when component destroyed
        this.subscriptions.forEach(s => s.unsubscribe());
      }
    
  • utilisation add()deSubscription

    subscriptions = new Subscription();
    
    this.subscriptions.add(subscribeOne);
    this.subscriptions.add(subscribeTwo);
    
    ngOnDestroy() {
      this.subscriptions.unsubscribe();
    }
    

A Subscriptionpeut détenir des abonnements enfants et les désinscrire en toute sécurité. Cette méthode gère les erreurs possibles (par exemple si des abonnements enfants sont nuls).

J'espère que cela t'aides.. :)

ganesh045
la source
0

Le package SubSink, une solution simple et cohérente de désinscription

Comme personne d'autre ne l'a mentionné, je veux recommander le package Subsink créé par Ward Bell: https://github.com/wardbell/subsink#readme .

Je l'ai utilisé sur un projet où nous sommes plusieurs développeurs qui l'utilisent tous. Cela aide beaucoup d'avoir une méthode cohérente qui fonctionne dans toutes les situations.

SnorreDan
la source
0

Pour les observables qui se terminent directement après avoir émis le résultat comme AsyncSubjectou par exemple les observables des requêtes http et ainsi vous n'avez pas besoin de vous désinscrire. Cela ne fait pas de mal de les appeler unsubscribe(), mais si l'observable est closedla méthode de désabonnement ne fera tout simplement rien :

if (this.closed) {
  return;
}

Lorsque vous avez des observables à longue durée de vie qui émettent plusieurs valeurs dans le temps (comme par exemple a BehaviorSubjectou a ReplaySubject), vous devez vous désabonner pour éviter les fuites de mémoire.

Vous pouvez facilement créer un observable qui se termine directement après avoir émis un résultat à partir de ces observables à longue durée de vie à l'aide d'un opérateur de tuyau. Dans certaines réponses, le take(1)tuyau est mentionné. Mais je préfère la first()pipe . La différence take(1)est qu'elle:

envoyer un EmptyErrorrappel d'erreur à l'observateur si l'observable se termine avant l'envoi de la prochaine notification.

Un autre avantage du premier canal est que vous pouvez passer un prédicat qui vous aidera à renvoyer la première valeur qui satisfait certains critères:

const predicate = (result: any) => { 
  // check value and return true if it is the result that satisfies your needs
  return true;
}
observable.pipe(first(predicate)).subscribe(observer);

First se terminera directement après avoir émis la première valeur (ou lorsque vous passez un argument de fonction la première valeur qui satisfait votre prédicat), il n'est donc pas nécessaire de vous désinscrire.

Parfois, vous ne savez pas si vous avez ou non une longue durée de vie observable. Je ne dis pas que c'est une bonne pratique, mais vous pouvez alors toujours ajouter le firstcanal juste pour vous assurer que vous n'aurez pas besoin de vous désabonner manuellement. L'ajout d'un firsttuyau supplémentaire sur un observable qui n'émettra qu'une seule valeur ne fait pas de mal.

Pendant le développement, vous pouvez utiliser le singlecanal qui échouera si la source observable émet plusieurs événements. Cela peut vous aider à explorer le type d'observable et s'il est nécessaire de vous désinscrire ou non.

observable.pipe(single()).subscribe(observer);

Les firstet singlesemblent très similaires, les deux canaux peuvent prendre un prédicat facultatif mais les différences sont importantes et bien résumées dans cette réponse de stackoverflow ici :

Première

Emettra dès que le premier élément apparaîtra. Se terminera juste après cela.

Célibataire

Échouera si la source observable émet plusieurs événements.


Remarque J'ai essayé d'être aussi précis et complet que possible dans ma réponse en faisant référence à la documentation officielle, mais veuillez commenter s'il manque quelque chose d'important ...

Se flétrir
la source
-1

--- Mettre à jour la solution Angular 9 et Rxjs 6

  1. Utilisation unsubscribeau cours du ngDestroycycle de vie du composant angulaire
class SampleComponent implements OnInit, OnDestroy {
  private subscriptions: Subscription;
  private sampleObservable$: Observable<any>;

  constructor () {}

  ngOnInit(){
    this.subscriptions = this.sampleObservable$.subscribe( ... );
  }

  ngOnDestroy() {
    this.subscriptions.unsubscribe();
  }
}
  1. Utilisation takeUntildans Rxjs
class SampleComponent implements OnInit, OnDestroy {
  private unsubscribe$: new Subject<void>;
  private sampleObservable$: Observable<any>;

  constructor () {}

  ngOnInit(){
    this.subscriptions = this.sampleObservable$
    .pipe(takeUntil(this.unsubscribe$))
    .subscribe( ... );
  }

  ngOnDestroy() {
    this.unsubscribe$.next();
    this.unsubscribe$.complete();
  }
}
  1. pour une action que vous appelez, ngOnInitcela ne se produit qu'une seule fois lorsque le composant init.
class SampleComponent implements OnInit {

  private sampleObservable$: Observable<any>;

  constructor () {}

  ngOnInit(){
    this.subscriptions = this.sampleObservable$
    .pipe(take(1))
    .subscribe( ... );
  }
}

Nous avons également des asynctuyaux. Mais, celui-ci utilise sur le modèle (pas dans le composant Angular).

Hoang Tran Son
la source
Votre premier exemple est incomplet.
Paul-Sebastian Manole