Angular / RxJs Quand dois-je me désabonner de "L'Abonnement"

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

économiser tous les abonnements introduit beaucoup de désordre dans le code de Composant.

guide client HTTP ignorez 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:

éventuellement, nous naviguerons ailleurs. Le routeur supprimera ce composant du DOM et le détruira. Nous devons nettoyer derrière nous avant que cela arrive. Nous devons nous désinscrire avant Qu'Angular ne détruise le composant. Ne pas le faire pourrait créer une fuite de mémoire.

Nous vous désinscrire de notre Observable dans le ngOnDestroy la mé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();
}
470
demandé sur Jota.Toledo 2016-06-24 10:52:45

14 réponses

--- Edition 4 - Des Ressources Supplémentaires (2018/09/01)

dans un épisode récent de Adventures in Angular Ben Lesh et Ward Bell discutent des questions entourant la façon et le moment de se désabonner d'un composant. La discussion commence à environ 1: 05: 30.

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

en réponse Ben mentionne qu'Il ya des discussions en ce moment pour permettre Observables pour s'accrocher aux événements du cycle de vie de la composante angulaire et Ward suggère un Observable d'événements du cycle de vie auquel un composant pourrait souscrire comme un moyen de savoir quand compléter Observables maintenus en tant qu'état interne du composant.

cela dit, nous avons surtout besoin de solutions maintenant donc, ici, sont quelques-uns des autres ressources.

  1. Une recommandation pour le takeUntil() modèle de RxJs membre de l'équipe noyau Nicolas Jamieson et un règle tslint pour aider à l'appliquer. https://blog.angularindepth.com/rxjs-avoiding-takeuntil-leaks-fb5182d047ef

  2. paquet 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 des constructions AOT (mais nous devrions tous faire AOT maintenant). https://github.com/smnbbrv/ngx-rx-collector

  4. Custom directive *ngSubscribe qui fonctionne comme async pipe mais crée une vue intégrée dans votre modèle de sorte que vous pouvez vous référer à la valeur 'unwrapped' tout au long de 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 caractéristique et composants de présentation devrait être considérée. Vous pouvez alors | async L'Observable de la composante caractéristique en Input de la composante Présentation, ce qui signifie qu'aucun abonnement n'est nécessaire nulle part. En savoir plus sur cette approche ici

--- Edition 3 - Le "Officielle" De La Solution (2017/04/09)

J'ai parlé avec Ward Bell de cette question à NGConf (je lui ai même montré cette réponse qu'il a dit être correcte) mais il m'a dit que l'équipe de Docs pour Angular avait une solution à cette question qui est inédit (bien qu'ils travaillent sur l'obtenir approuvé). Il m'a également dit que je pourrais mettre à jour ma réponse SO avec la prochaine recommandation officielle.

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

nous appelons alors this.ngUnsubscribe.next(); this.ngUnsubscribe.complete(); dans nos méthodes ngOnDestroy() .

la sauce secrète (comme noté déjà par @metamaker ) est d'appeler takeUntil(this.ngUnsubscribe) avant chacun de nos .subscribe() appels qui garantiront que tous les abonnements seront nettoyés lorsque le composant est 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();
    }
}

Note: il est important d'ajouter l'opérateur takeUntil comme dernier pour prévenir les fuites avec des observables intermédiaires dans la chaîne de l'opérateur.

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

Source 5

le tutoriel angulaire, 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, de sorte que nous n'avons pas besoin de se désabonner de la route params Observable."- Marque Rajcok

Voici un discussion sur les questions Github pour les docs angulaires concernant Routeur Observables où Ward Bell mentionne que la clarification pour tout cela est en cours.

- - - Edit 1

Source 4

dans ce vidéo de NgEurope Rob Wormald dit également que vous n'avez pas besoin de vous désabonner de routeur Observables. Il mentionne également http service et ActivatedRoute.params dans cette vidéo de novembre 2016 .

--- Réponse Originale À Cette Question

TLDR:

pour cette question il y a (2) types de Observables - fini valeur et infinie valeur.

http Observables produire finis (1) valeurs et quelque chose comme un DOM event listener Observables produire infinite valeurs.

si vous appelez manuellement subscribe (n'utilisant pas async pipe), puis unsubscribe de infinite Observables .

Ne vous inquiétez pas finis , RxJs de prendre soin d'eux.

Source 1

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

"1519440920 affirme-t-Il (j'ai réorganisé pour plus de clarté, et c'est moi qui souligne)

si son une séquence de valeur unique (comme une requête http) le nettoyage manuel est inutile (en supposant que vous vous abonnez dans le contrôleur manuellement)

je devrais dire "si c'est un séquence complète " (dont l'unique valeur de séquences, une la http, ne font qu'un)

si sa séquence infinie , vous devez vous désinscrire ce que le tuyau async fait pour vous

il mentionne également dans ce vidéo youtube sur Observables que they clean up after themselves ... dans le contexte des Observables que complete (comme des Promesses, qui est toujours complet parce qu'ils produisent toujours 1 Valeur et fin - nous ne nous sommes jamais inquiétés de se désinscrire des promesses pour s'assurer qu'ils nettoient xhr les auditeurs d'événements, Non?).

Source 2

aussi dans le Guide de Rangle à L'angle 2 il lit

Dans la plupart des cas nous n'aurons pas besoin d'appeler explicitement la méthode de désabonnement à moins que nous vous voulez annuler plus tôt ou notre Observable a une plus longue durée de vie que notre abonnement. Le comportement par défaut des opérateurs observables est de disposer de l'abonnement dès que .complet() ou .les messages d'erreur() sont publiés. Gardez à l'esprit que RxJS a été conçu pour être utilisé dans un "feu et oublier" mode la plupart du temps.

quand l'expression our Observable has a longer lifespan than our subscription s'applique-t-elle?

Elle s'applique lorsqu'un abonnement est créé à l'intérieur d'un composant qui est détruit avant (ou peu de temps avant) la Observable complète.

je lis ceci comme signifiant que si nous souscrivons à une requête http ou à un observable qui émet 10 valeurs et que notre composant est détruit avant que la requête http retourne ou que les 10 valeurs aient été émises, nous sommes toujours ok!

lorsque la demande revient ou que la 10ème valeur est finalement émise, le Observable sera complet et toutes les ressources seront nettoyer.

Source 3

si nous regardons cet exemple du même Guide de Rangle, nous pouvons voir que le Subscription à route.params nécessite un unsubscribe() parce que nous ne savons pas quand ceux params cesseront de changer (émission de nouvelles valeurs).

le composant pourrait être détruit en naviguant à l'extérieur, auquel cas les params de route seront probablement encore changer (ils pourraient changer techniquement jusqu'à la fin de l'application) et les ressources allouées dans l'abonnement seraient toujours allouées parce qu'il n'y a pas eu un completion .

717
répondu seangwright 2018-09-01 17:12:21

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

import {Subject} from "rxjs/Subject";

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

    constructor(protected titleService: TitleService)
    {
        this.titleService.emitter1$
            .takeUntil(this.componentDestroyed$)
            .subscribe(
            (data: any) =>
            {
                // ... do something 1
            }
        );

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

        // ...

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

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

Alternative approach , qui a été proposé par @acumartini dans les commentaires , utilise takeWhile au lieu de takeUntil . Vous vous pouvez le préférer, mais n'oubliez pas que de cette façon votre exécution Observable ne sera pas annulée sur ngDestroy de votre composant (par exemple quand vous faites des calculs fastidieux ou attendez les données du serveur). Méthode, qui est basé sur takeUntil , n'a pas cet inconvénient et conduit à l'annulation immédiate de la demande. merci à @AlexChe pour les explications détaillées dans les commentaires .

donc voici le code:

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

    constructor(protected titleService: TitleService)
    {
        this.titleService.emitter1$
            .takeWhile(() => this.alive)
            .subscribe(
            (data: any) =>
            {
                // ... do something 1
            }
        );

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

        // ...

        this.titleService.emitterN$
            .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;
    }
}
62
répondu metamaker 2017-09-25 09:01:32

la classe D'Abonnement a une caractéristique intéressante:

représente une ressource disponible, telle que l'exécution D'un Observable. Un Abonnement a une méthode importante, se désabonner, qui ne prend aucun argument et dispose simplement de la ressource détenue par l'abonnement.

de plus, les abonnements peuvent être regroupés par la méthode add (), qui joindra un Abonnement enfant à l'abonnement courant Abonnement. Lorsqu'un Abonnement n'est pas souscrit, tous ses enfants (et ses petits-enfants) le sont également.

vous pouvez créer un objet d'Abonnement agrégé qui regroupe tous vos Abonnements. Pour ce faire, vous créez un Abonnement vide et vous y ajoutez des abonnements en utilisant la méthode add() . Lorsque votre composant est détruit, vous n'avez qu'à vous désinscrire de 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();
  }
}
39
répondu Steven Liekens 2018-08-17 20:21:52

cela dépend. Si en appelant someObservable.subscribe() , vous commencez à bloquer une ressource qui doit être libérée manuellement lorsque le cycle de vie de votre composant est terminé, alors vous devriez appeler theSubscription.unsubscribe() pour prévenir les fuites de mémoire.

regardons de plus près vos exemples:

getHero() renvoie le résultat de http.get() . Si vous regardez dans l'angle 2 code source , 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 _xhr est spécifique à la plate-forme mais je pense qu'il est sûr de supposer qu'il s'agit d'un XMLHttpRequest() dans votre cas.

normalement, il s'agit d'une preuve suffisante pour justifier un appel manuel unsubscribe() . Mais selon ce "WHATWG spec , le XMLHttpRequest() est sujet aux ordures collection une fois qu'il est" fait", même s'il y a des écouteurs d'événements attachés à lui. Donc je suppose que c'est pourquoi angular 2 Guide officiel omet unsubscribe() et laisse GC nettoyer les écouteurs.

comme pour votre second exemple, il dépend de la mise en œuvre de params . À partir d'aujourd'hui, le guide officiel angulaire ne montre plus de désabonnement de params . J'ai regardé dans src encore et trouvé que params est un juste BehaviorSubject . Étant donné qu'aucun récepteur d'événements ou minuterie n'a été utilisé et qu'Aucune variable globale n'a été créée, il devrait être prudent d'omettre unsubscribe() .

le résultat final de votre question Est que toujours appeler unsubscribe() comme une protection contre les fuites de mémoire, à moins que vous ne soyez certain que l'exécution de l'observable ne crée pas de variables globales, n'ajoute pas d'écouteurs d'événements, n'installe pas de minuteries, ou ne fasse rien d'autre qui entraîne des fuites de mémoire.

quand en doute, regardez dans la mise en œuvre de cette observable. Si l'observable a écrit une certaine logique de nettoyage dans son unsubscribe() , qui est habituellement la fonction qui est retournée par le constructeur, alors vous avez de bonnes raisons d'envisager sérieusement d'appeler unsubscribe() .

13
répondu evenstar 2016-12-01 07:14:40

Certaines des meilleures pratiques concernant les phénomènes observables désinscriptions à l'intérieur des composants Angulaires:

une citation de Routing & Navigation

lorsque vous vous abonnez à un observable d'un composant, vous vous arrangez presque toujours pour vous désabonner lorsque le composant est détruit.

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

la route activée 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 que la route activée injectée meurt avec.

n'hésitez pas à vous désabonner de toute façon. Il est inoffensif et jamais une mauvaise pratique.

et en répondant aux liens suivants:

j'ai recueilli des exemples de meilleures pratiques en ce qui concerne les non-inscriptions observables à l'intérieur des composants angulaires à partager avec vous:

  • http le désabonnement observable est conditionnel et nous devrions tenir compte des effets du 'subscribe callback' lancé après la destruction du composant au cas par cas. Nous savons que l'angular désabonne et nettoie le http observable lui-même (1) , (2) . Si cela est vrai du point de vue des ressources il ne raconte que la moitié de l'histoire. Disons que nous parlons d'appeler directement http à partir d'un composant, et la réponse http a pris plus de temps que nécessaire, donc l'Utilisateur a fermé le composant. Le gestionnaire subscribe() 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 laissent l'état de l'application cassé. Il peut aussi causer des exceptions si le code dans le callback essaie d'appeler quelque chose qui a juste été éliminés. Cependant en même temps parfois ils sont désirés. Disons que vous créez un client e-mail et que vous déclenchez un son lorsque l'e - mail est envoyé-Eh bien, vous voulez toujours que cela se produise même si le composant est fermé.( 8 ).
  • pas besoin de se désabonner des observables qui sont complets ou erronés. Toutefois, il n'y a pas de mal à le faire. (7) .
  • Utiliser AsyncPipe autant que possible parce qu'il se désabonne automatiquement de l'observable sur la destruction de Composant.
  • Se désabonner des observables ActivatedRoute comme route.params s'ils sont abonnés à l'intérieur d'une composante imbriquée (ajoutée à l'intérieur de tpl avec le sélecteur de composants) ou dynamique car ils peuvent être abonnés plusieurs fois aussi longtemps que le composant parent/hôte existe. Pas besoin de vous désabonner dans d'autres scénarios, comme mentionné dans la citation ci-dessus de Routing & Navigation docs.
  • Se désabonner des observables globaux partagés entre des composants qui sont exposés à travers un service angulaire par exemple car ils peuvent être souscrits plusieurs fois aussi longtemps que le composant est initialisé.
  • pas besoin de se désabonner des observables internes d'un service d'application scoped puisque ce service ne se fait jamais détruire, à moins que votre application entière se fait détruire, il n'y a aucune raison réelle de vous désabonner et il n'y a aucune chance de fuites de mémoire. (6) .



    Note: concernant les services scoped, I. e fournisseurs de composants, ils sont détruits lorsque le composant est détruit. Dans ce cas, si nous nous abonnons à n'importe quel observable à l'intérieur de ce fournisseur, nous devrions envisager de se désabonner en utilisant le OnDestroy crochet du cycle de vie qui sera appelé quand le service est détruit, selon les docs.
  • utiliser une technique abstraite pour éviter tout désordre de code qui pourrait résulter de désinscriptions. Vous pouvez gérer vos abonnements avec takeUntil (3) ou vous pouvez utiliser ce npm paquet mentionné à (4) la façon la plus facile de se désabonner des Observables dans L'angle .
  • toujours se désabonner de FormGroup observables comme form.valueChanges et form.statusChanges
  • toujours se désabonner des observables de Renderer2 service comme renderer2.listen
  • Se désabonner de tous les autres observables comme un pas de garde de fuite de mémoire jusqu'à ce que les Docs angulaires nous disent explicitement quels observables sont inutiles pour être désabonnés (cochez la question: (5) Documentation pour RxJS Se désabonner (ouvert) ).
  • Bonus: toujours utiliser l'angle des façons de lier des événements comme HostListener comme des soins angulaires bien sur Enlever les écouteurs d'événement si nécessaire et empêche toute fuite de mémoire potentielle due aux fixations d'événement.

"a nice final tip : si vous ne savez pas si un observable est automatiquement désabonné/complété ou non, ajoutez un complete callback à subscribe(...) et vérifiez s'il est appelé lorsque le composant est détruit.

13
répondu Mouneer 2018-10-02 12:00:08

Angular 2 documents officiels fournit une explication pour quand se désabonner et quand il peut être ignoré en toute sécurité. Regardez ce lien:

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

cherchez le paragraphe avec le titre Parent et les enfants communiquent via un service et puis la boîte bleue:

notez que nous capturons l'abonnement et nous désabonnons lorsque le Component Astronaut est détruit. C'est une mémoire-garde de fuite étape. Il n'y a pas de risque réel dans cette application parce que la durée de vie d'un Component Astronaut est la même que la durée de vie de l'application elle-même. Cela ne serait pas toujours vrai dans une application plus complexe.

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

j'espère que cela vous aide.

6
répondu Cerny 2016-06-29 11:08:04

étant donné que la solution de seangwright (Edit 3) semble très utile, j'ai aussi trouvé pénible d'empaqueter cette fonctionnalité dans le composant de base, et j'ai suggéré à d'autres coéquipiers du projet de se rappeler d'appeler super() sur ngOnDestroy pour activer cette fonctionnalité.

cette réponse fournir un moyen de mettre libre de super appel, et faire" componentDestroyed$ " un noyau de composante 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 ensuite vous pouvez utiliser cette caractéristique 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');
    }

}
3
répondu Val 2017-04-24 05:34:10

basé sur: utilisant l'héritage de classe pour accrocher à la composante angulaire 2 cycle de vie

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

@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));
    }
}
3
répondu JoG 2018-02-23 14:54:31

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

voici une autre approche utilisant des enveloppes.

Warining: code expérimental

File subscreandguard.ts est utilisé pour créer une nouvelle extension Observable pour envelopper .subscribe() et dans lui envelopper ngOnDestroy() .

L'utilisation est la même que .subscribe() , à l'exception d'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;
  }
}

Ici est un composant avec deux abonnements, l'un avec l'enveloppe et l'autre sans. La seule mise en garde est qu'il doit mettre en œuvre OnDestroy (avec corps vide si désiré), sinon Angular ne sait pas appeler la version enveloppé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');
  }
}

Une démo plunker est ici

Note complé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 à 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;
  }
}
2
répondu Richard Matsen 2017-05-17 22:03:08

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

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() {}
}
2
répondu Scott Williams 2017-09-28 19:51:06

suite à la réponse de @seangwright , j'ai écrit une classe abstraite qui gère les abonnements" infinite "observables en 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 composante angulaire et appeler la méthode subscribe() comme suit:

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

il accepte aussi l'erreur et les callbacks complets comme d'habitude, un objet observer, ou pas de callbacks du tout. N'oubliez pas d'appeler super.ngOnDestroy() si vous nous appliquons également cette méthode dans le volet enfants.

trouver ici une référence supplémentaire par Ben Lesh: RxJS: ne pas désabonner .

2
répondu Mau Muñoz 2018-04-19 23:34:36

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

qui ne fonctionne pas pour L'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();
   }
 }
}
1
répondu Jeff Tham 2017-04-11 20:00:48

vous avez généralement besoin de se désabonner lorsque les composants sont détruits, mais Angular va le gérer de plus en plus que nous allons, par exemple dans la nouvelle version mineure D'Angular4, ils ont cette section pour le routage se désabonner:

avez-vous besoin de vous désabonner?

Comme décrit dans la Activedroute: le guichet unique pour la section d'information sur les itinéraires de la Routing & Navigation page, le routeur gère le observables, il fournit et localise les abonnements. Les abonnements sont nettoyé lorsque le composant est détruit, protection contre la mémoire fuites, de sorte que vous n'avez pas besoin de se désabonner de la route paramMap Observable.

aussi l'exemple ci-dessous est un bon exemple D'angle pour créer un composant et le détruire après, regardez comment le composant implémente OnDestroy, si vous avez besoin d'onInit, vous pouvez également l'implémenter dans votre composant ,comme les outils 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();
  }
}
1
répondu Alireza 2017-10-16 11:52:57

un autre petit ajout aux situations susmentionnées est le suivant:

  • toujours se désabonner, lorsque de nouvelles valeurs dans le flux souscrit n'est plus nécessaire ou n'a pas d'importance, il en résultera beaucoup moins de déclencheurs et une augmentation de la performance dans quelques cas. Les cas tels que les composants où les données/événements souscrits n'existent plus ou un nouvel abonnement à un tout nouveau flux est requis (rafraîchissement, etc.) est un bon exemple de désabonnement.
1
répondu kg11 2018-06-16 12:49:59