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 lengOnDestroy
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();
}
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.
-
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 -
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 pendantngOnDestroy
. https://github.com/NetanelBasal/ngx-take-until-destroy -
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
-
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
.
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;
}
}
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();
}
}
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()
.
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:
- (1) Devrais-je me désinscrire Angulaire 2 Http Observables?
- (2) Est-il nécessaire de vous désabonner à partir des observables créé par les méthodes Http?
- (3) RxJS: Ne pas se Désabonner
- (4) la façon la plus facile de se désabonner des Observables en angle
- (5) Documentation pour RxJS Unsubscribing
- (6) se désinscrire dans un service est en quelque sorte inutile puisqu'il n'y a aucune chance de fuites de mémoire
- (7) nous avons besoin de vous désabonner à partir d'observables qui complète/des erreurs?
- (8) Un commentaire à propos de la
http
observables
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 lehttp
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 directementhttp
à partir d'un composant, et la réponsehttp
a pris plus de temps que nécessaire, donc l'Utilisateur a fermé le composant. Le gestionnairesubscribe()
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
commeroute.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 deRouting & 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 leOnDestroy
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 cenpm
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 commeform.valueChanges
etform.statusChanges
- toujours se désabonner des observables de
Renderer2
service commerenderer2.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.
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.
é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');
}
}
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));
}
}
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;
}
}
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() {}
}
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 .
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();
}
}
}
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();
}
}
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.