Quando dovrei memorizzare le istanze di Subscription
e invocare unsubscribe()
durante il ciclo di vita di NgOnDestroy e quando posso semplicemente ignorarle?
Il salvataggio di tutti gli abbonamenti introduce un sacco di problemi nel codice componente.
La guida client HTTP ignora le iscrizioni in questo modo:
getHeroes() { this.heroService.getHeroes() .subscribe( heroes => this.heroes = heroes, error => this.errorMessage = error); }
Allo stesso tempo, la Guida di navigazione e itinerario dice che:
Alla fine, navigheremo da qualche altra parte. Il router rimuoverà questo componente dal DOM e lo distruggerà. Abbiamo bisogno di ripulire noi stessi prima che ciò accada. Nello specifico, dobbiamo annullare l’iscrizione prima che Angular distrugga il componente. In caso contrario, potrebbe verificarsi una perdita di memoria.
- Errore di tipo osservabile: imansible leggere la proprietà di non definito
- La sequenza RxJS equivale a promise.then ()?
- Angular 2 beta.17: la 'mappa' di proprietà non esiste sul tipo 'Observable '
- Promessa vs Osservabile
- Come ottenere il valore corrente di RxJS sobject o osservabile?
ngOnDestroy
l’ngOnDestroy
alngOnDestroy
metodoObservable
nel metodongOnDestroy
.
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(); }
In un recente episodio di Adventures in Angular, Ben Lesh e Ward Bell discutono i problemi relativi a come / quando annullare l’iscrizione a un componente. La discussione inizia alle 1:05:30.
Ward dice che in right now there's an awful takeUntil dance that takes a lot of machinery
e Shai Reznik menziona Angular handles some of the subscriptions like http and routing
.
In risposta Ben ricorda che in questo momento ci sono discussioni per consentire agli osservabili di collegarsi agli eventi del ciclo di vita dei componenti angolari e Ward suggerisce un Osservabile degli eventi del ciclo di vita che un componente potrebbe sottoscrivere come un modo per sapere quando completare gli osservabili mantenuti come stato interno dei componenti.
Detto questo, per lo più abbiamo bisogno di soluzioni ora, quindi ecco alcune altre risorse.
Una raccomandazione per il modello takeUntil()
dal membro del team principale di RxJs Nicholas Jamieson e una regola di tslint per aiutare a rafforzarla. https://blog.angularindepth.com/rxjs-avoiding-takeuntil-leaks-fb5182d047ef
Pacchetto npm leggero che espone un operatore Observable che prende un’istanza di componente ( this
) come parametro e ngOnDestroy
automaticamente l’annullamento durante ngOnDestroy
. https://github.com/NetanelBasal/ngx-take-until-destroy
Un’altra variante di quanto sopra con un’ergonomia leggermente migliore se non stai facendo build AOT (ma dovremmo fare tutti AOT ora). https://github.com/smnbbrv/ngx-rx-collector
Direttiva personalizzata *ngSubscribe
che funziona come pipe asincrono ma crea una vista incorporata nel modello in modo da poter fare riferimento al valore ‘unwrapped’ in tutto il modello. https://netbasal.com/diy-subscription-handling-directive-in-angular-c8f6e762697f
takeUntil()
in un commento al blog di Nicholas che l’uso takeUntil()
di takeUntil()
potrebbe essere un segnale che il componente sta cercando di fare troppo e che la separazione dei componenti esistenti in componenti Feature e Presentational dovrebbe essere presa in considerazione. Puoi quindi | async
| async
il componente Osservabile dal Feature in un Input
del componente Presentational, il che significa che non sono necessarie iscrizioni ovunque. Leggi di più su questo approccio qui
Ho parlato con Ward Bell di questa domanda a NGConf (gli ho persino mostrato questa risposta che ha detto fosse corretta) ma mi ha detto che il team di doc per Angular aveva una soluzione a questa domanda che non è stata pubblicata (sebbene stiano lavorando per ottenerlo approvato ). Mi ha anche detto che potrei aggiornare la mia risposta SO con la prossima raccomandazione ufficiale.
La soluzione che dovremmo usare in futuro è aggiungere un private ngUnsubscribe = new Subject();
campo a tutti i componenti che hanno .subscribe()
chiama Observable
s all’interno del loro codice di class.
Quindi chiamiamo this.ngUnsubscribe.next(); this.ngUnsubscribe.complete();
this.ngUnsubscribe.next(); this.ngUnsubscribe.complete();
nei nostri metodi ngOnDestroy()
.
La salsa segreta (come già notato da @metamaker ) consiste nel chiamare takeUntil(this.ngUnsubscribe)
prima di ciascuna delle nostre chiamate .subscribe()
che garantirà che tutte le sottoscrizioni vengano ripulite quando il componente viene distrutto.
Esempio:
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(); } }
Nota: è importante aggiungere l’operatore takeUntil
come ultimo per evitare perdite con osservabili intermedi nella catena dell’operatore.
Fonte 5
Il tutorial Angular, il capitolo Routing ora afferma quanto segue: “Il Router gestisce le osservabili che fornisce e localizza le sottoscrizioni. Le sottoscrizioni vengono ripulite quando il componente viene distrutto, proteggendo da perdite di memoria, quindi non è necessario annullare l’iscrizione da il percorso params Observable. ” – Mark Rajcok
Ecco una discussione sui problemi di Github per i documenti angolari relativi a Router Observables, dove Ward Bell menziona che il chiarimento di tutto questo è in corso.
Fonte 4
In questo video di NgEurope, Rob Wormald afferma anche che non è necessario annullare l’iscrizione a Router Observables. Menziona anche il servizio http
e ActivatedRoute.params
in questo video da novembre 2016 .
TLDR:
Per questa domanda ci sono (2) tipi di Observables
– valore finito e valore infinito .
http
Observables
produce valori finiti (1) e qualcosa come un event listener
DOM Gli Observables
producono valori infiniti .
Se si chiama subscribe
manualmente (non si utilizza la pipe asincrona), si unsubscribe
da Observables
infiniti .
Non preoccuparti di quelli finiti , gli RxJs
si prenderanno cura di loro.
Fonte 1
Ho rintracciato qui una risposta di Rob Wormald in Angular’s Gitter.
Dichiara (mi sono riorganizzato per chiarezza e l’enfasi è mia)
se è una sequenza a valore singolo (come una richiesta http) la pulizia manuale non è necessaria (presupponendo che si iscriva manualmente nel controller)
dovrei dire “se è una sequenza che completa ” (di cui le sequenze a valore singolo, a la http, sono una sola)
se è una sequenza infinita , è necessario annullare l’ iscrizione che il tubo asincrono fa per te
Inoltre menziona in questo video di YouTube su Observables che they clean up after themselves
… nel contesto degli Observables che complete
(come Promises, che si completano sempre perché producono sempre un valore e finiscono) non ci siamo mai preoccupati di annullare l’iscrizione da Promises a assicurati che xhr
ascoltatori di eventi xhr
, giusto?).
Fonte 2
Anche nella guida di Rangle per Angular 2 si legge
Nella maggior parte dei casi non sarà necessario chiamare esplicitamente il metodo di annullamento della sottoscrizione, a meno che non desideriamo annullare anticipatamente o che il nostro Osservabile abbia una durata maggiore rispetto al nostro abbonamento. Il comportamento predefinito degli operatori Observable è quello di eliminare la sottoscrizione non appena vengono pubblicati i messaggi .complete () o .error (). Tieni presente che RxJS è stato progettato per essere utilizzato in modo “fuoco e dimentica” la maggior parte del tempo.
Quando la frase ” our Observable has a longer lifespan than our subscription
?
Si applica quando viene creata una sottoscrizione all’interno di un componente che viene distrutto prima (o non “lungo” prima) che l’ Observable
completa.
Leggo questo nel senso che se ci iscriviamo ad una richiesta http
o ad un osservabile che emette 10 valori e il nostro componente viene distrutto prima che la richiesta http
ritorni o siano stati emessi i 10 valori, siamo ancora ok!
Quando la richiesta viene restituita o il decimo valore viene finalmente emesso, l’ Observable
verrà completato e tutte le risorse verranno ripulite.
Fonte 3
Se guardiamo questo esempio dalla stessa guida di Rangle possiamo vedere che l’ Subscription
a route.params
richiede un unsubscribe()
perché non sappiamo quando quei params
smetteranno di cambiare (emettendo nuovi valori).
Il componente potrebbe essere distrutto navigando lontano, nel qual caso i parametri del percorso probabilmente cambieranno (potrebbero cambiare tecnicamente fino alla fine dell’app) e le risorse allocate nell’abbonamento saranno ancora allocate perché non è stato completion
.
Non è necessario avere un sacco di abbonamenti e disiscriversi manualmente. Usa RxJS.Subject e takeUntil combo per gestire abbonamenti come 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 = 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(); } }
L’approccio alternativo , che è stato proposto da @acumartini nei commenti , utilizza takeWhile anziché takeUntil . Potresti preferirlo, ma ricorda che in questo modo la tua esecuzione osservabile non verrà annullata su ngDestroy del tuo componente (ad esempio quando esegui calcoli che richiedono molto tempo o attendi dati dal server). Il metodo, che è basato su takeUntil , non ha questo inconveniente e porta all’annullamento immediato della richiesta. Grazie a @AlexChe per la spiegazione dettagliata nei commenti .
Quindi ecco il codice:
@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 class Subscription ha una caratteristica interessante:
Rappresenta una risorsa usa e getta, come l’esecuzione di un Osservabile. Un abbonamento ha un metodo importante, annullare l’iscrizione, che non accetta argomenti e dispone solo della risorsa detenuta dall’abbonamento.
Inoltre, le sottoscrizioni possono essere raggruppate insieme attraverso il metodo add (), che allegherà una Sottoscrizione subordinata alla Sottoscrizione corrente. Quando un abbonamento è annullato, tutti i suoi figli (e i suoi nipoti) saranno annullati.
È ansible creare un object di sottoscrizione aggregato che raggruppa tutte le sottoscrizioni. A tale scopo, crea una sottoscrizione vuota e aggiungi abbonamenti utilizzando il suo metodo add()
. Quando il componente viene distrutto, è necessario solo annullare l’iscrizione alla sottoscrizione aggregata.
@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(); } }
Dipende. Se si chiama someObservable.subscribe()
, si inizia a contenere alcune risorse che devono essere liberate manualmente quando il ciclo di vita del componente è finito, quindi si dovrebbe chiamare theSubscription.unsubscribe()
per evitare perdite di memoria.
Diamo un’occhiata più da vicino ai tuoi esempi:
getHero()
restituisce il risultato di http.get()
. Se si guarda il codice sorgente angular 2, http.get()
crea due listener di eventi:
_xhr.addEventListener('load', onLoad); _xhr.addEventListener('error', onError);
e chiamando unsubscribe()
, è ansible annullare la richiesta così come gli ascoltatori:
_xhr.removeEventListener('load', onLoad); _xhr.removeEventListener('error', onError); _xhr.abort();
Nota che _xhr
è specifico per la piattaforma, ma penso che sia sicuro assumere che sia XMLHttpRequest()
nel tuo caso.
Normalmente, questa è una prova sufficiente per giustificare una chiamata di unsubscribe()
manuale unsubscribe()
. Ma secondo questa specifica WHATWG , il XMLHttpRequest()
è sobject alla garbage collection una volta che è “finito”, anche se ci sono listener di eventi collegati ad esso. Quindi suppongo che è per questo che la guida ufficiale di angular 2 omette l’ unsubscribe()
dell’iscrizione unsubscribe()
e consente a GC di ripulire gli ascoltatori.
Per quanto riguarda il tuo secondo esempio, dipende dall’implementazione dei params
. A partire da oggi, la guida angular ufficiale non mostra più l’annullamento params
dai params
. Ho controllato di nuovo src e ho scoperto che params
è solo un BehaviorSubject . Poiché non sono stati utilizzati listener o timer di eventi e non sono state create variabili globali, è opportuno omettere l’ unsubscribe()
.
La linea di fondo per la tua domanda è che chiama sempre unsubscribe()
come guardia contro la perdita di memoria, a meno che tu non sia certo che l’esecuzione dell’osservabile non crei variabili globali, aggiungi ascoltatori di eventi, imposta timer o faccia qualsiasi altra cosa che risulta in perdite di memoria.
In caso di dubbio, esamina l’implementazione di tale osservabile. Se l’osservabile ha scritto una logica di ripulitura nella sua unsubscribe()
, che di solito è la funzione restituita dal costruttore, allora hai buone ragioni per prendere seriamente in considerazione la possibilità di chiamare unsubscribe()
.
Alcune delle migliori pratiche relative alle disiscrizioni osservabili all’interno dei componenti angolari:
Una citazione da Routing & Navigation
Quando ti iscrivi a un osservabile in un componente, quasi sempre prevedi di annullare l’iscrizione quando il componente viene distrutto.
Ci sono alcuni osservabili eccezionali dove questo non è necessario. Le osservabili di ActivatedRoute sono tra le eccezioni.
ActivatedRoute e i suoi osservabili sono isolati dal router stesso. Il router distrugge un componente instradato quando non è più necessario e l’ActivatedRoute iniettato muore con esso.
Sentiti libero di disiscriverti comunque. È innocuo e mai una ctriggers pratica.
E nel rispondere ai seguenti link:
http
Ho raccolto alcune delle migliori pratiche relative alle disiscrizioni osservabili all’interno di componenti Angolari da condividere con voi:
http
disiscrizione http
osservabile è condizionale e dovremmo considerare gli effetti del “callback di sottoscrizione” eseguito dopo che il componente è stato distrutto caso per caso. Sappiamo che l’angular disiscrive e pulisce l’osservabile http
stesso (1) , (2) . Mentre questo è vero dal punto di vista delle risorse, dice solo metà della storia. Diciamo che stiamo parlando di chiamare direttamente http
da un componente, e la risposta http
richiesto più tempo del necessario, quindi l’utente ha chiuso il componente. Il gestore subscribe()
verrà comunque chiamato anche se il componente è chiuso e distrutto. Questo può avere effetti collaterali indesiderati e negli scenari peggiori lasciare lo stato dell’applicazione rotto. Può anche causare eccezioni se il codice nel callback tenta di chiamare qualcosa che è stato appena eliminato. Tuttavia allo stesso tempo occasionalmente sono desiderati. Ad esempio, supponiamo che tu stia creando un client di posta elettronica e attivi un suono quando l’e-mail ha terminato l’invio – beh, vorresti che succedesse anche se il componente è chiuso ( 8 ). AsyncPipe
il più ansible perché si AsyncPipe
automaticamente dalla distruzione osservabile del componente. route.params
osservabili di ActivatedRoute
come route.params
se sono iscritti all’interno di un nidificato (aggiunto all’interno di tpl con il selettore di componenti) o di un componente dinamico in quanto possono essere sottoscritti molte volte finché esiste il componente principale / host. Non è necessario annullare l’iscrizione da loro in altri scenari come menzionato nella citazione sopra dai documenti di Routing & Navigation
. takeUntil
(3) o puoi usare questo pacchetto npm
cui al punto (4) Il modo più semplice per annullare l’iscrizione a Observables in Angular . FormGroup
sempre l’iscrizione FormGroup
osservabili di form.valueChanges
come form.valueChanges
e form.statusChanges
Renderer2
come renderer2.listen
HostListener
alle preoccupazioni angolari sulla rimozione degli ascoltatori di eventi, se necessario, e prevenire potenziali perdite di memoria dovute ai binding di eventi. Un buon consiglio finale : se non sai se un osservabile viene automaticamente annullato / completato o meno, aggiungi un callback complete
per subscribe(...)
e controlla se viene chiamato quando il componente viene distrutto.
La documentazione ufficiale di Angular 2 fornisce una spiegazione su quando annullare l’iscrizione e quando può essere tranquillamente ignorata. Dai un’occhiata a questo link:
https://angular.io/docs/ts/latest/cookbook/component-communication.html#!#bidirectional-service
Cerca il paragrafo con l’intestazione Parent e children comunicano tramite un servizio e poi la casella blu:
Si noti che acquisiamo la sottoscrizione e annulliamo l’iscrizione quando DistronautComponent viene distrutto. Questo è un passo per la perdita di memoria. Non ci sono rischi reali in questa app perché la durata di un AstronautComponent è uguale alla durata della stessa app. Ciò non sarebbe sempre vero in un’applicazione più complessa.
Non aggiungiamo questa guardia a MissionControlComponent perché, in quanto genitore, controlla la durata del MissionService.
Spero che questo ti aiuta.
Dal momento che la soluzione di seangwright (Modifica 3) sembra essere molto utile, ho anche trovato un problema impacchettare questa funzione nel componente di base e suggerire ad altri membri del team di progetto di ricordare di chiamare super () su ngOnDestroy per triggersre questa funzione.
Questa risposta fornisce un modo per liberarsi dalla super chiamata e rendere “componenteDestroyed $” un nucleo del componente base.
class BaseClass { protected componentDestroyed$: Subject = new Subject (); 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() {} }
E quindi puoi usare questa funzione liberamente, ad esempio:
@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'); } }
Basato su: utilizzo dell’ereditarietà della class per eseguire il collegamento al ciclo di vita di 2 componenti angolari
Un altro approccio generico:
export abstract class UnsubscribeOnDestroy implements OnDestroy { protected d$: Subject; constructor() { this.d$ = new Subject(); const f = this.ngOnDestroy; this.ngOnDestroy = () => { f(); this.d$.next(); this.d$.complete(); }; } public ngOnDestroy() { // no-op } }
La risposta di Edit # 3 (e le varianti) ufficiali funziona bene, ma la cosa che mi dà è la ‘fangosa’ della logica di business attorno all’abbonamento osservabile.
Ecco un altro approccio usando i wrapper.
Warining: codice sperimentale
File subscribeAndGuard.ts è usato per creare una nuova estensione Observable per avvolgere .subscribe()
e al suo interno per avvolgere ngOnDestroy()
.
L’utilizzo è uguale a .subscribe()
, ad eccezione di un primo parametro aggiuntivo che fa riferimento al componente.
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 { subscribeAndGuard: typeof subscribeAndGuard; } }
Ecco un componente con due abbonamenti, uno con il wrapper e uno senza. L’unica avvertenza è che deve implementare OnDestroy (con il corpo vuoto se lo si desidera), altrimenti Angular non sa di chiamare la versione avvolta.
import { Component, OnInit, OnDestroy } from '@angular/core'; import { Observable } from 'rxjs/Observable'; import 'rxjs/Rx'; import './subscribeAndGuard'; @Component({ selector: 'app-subscribing', template: 'Subscribing component is active
', }) 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 plunker demo è qui
Una nota aggiuntiva: Modifica 3 – La soluzione ‘ufficiale’, può essere semplificata usando takeWhile () invece di takeUntil () prima delle sottoscrizioni e un semplice booleano piuttosto che un altro osservabile in 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; } }
Mi piacciono le ultime due risposte, ma ho riscontrato un problema se la sottoclass ha fatto riferimento a "this"
in ngOnDestroy
.
L’ho modificato per essere questo, e sembra che abbia risolto il problema.
export abstract class BaseComponent implements OnDestroy { protected componentDestroyed$: Subject; constructor() { this.componentDestroyed$ = new Subject (); 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() {} }
I tried seangwright’s solution (Edit 3)
That is not working for Observable that created by timer or interval.
However, i got it working by using another approach:
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 = []; 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(); } } }
You usually need to unsubscribe when the components get destroyed, but Angular is going to handle it more and more as we go, for example in new minor version of Angular4, they have this section for routing unsubscribe:
Do you need to unsubscribe?
As described in the ActivatedRoute: the one-stop-shop for route information section of the Routing & Navigation page, the Router manages the observables it provides and localizes the subscriptions. The subscriptions are cleaned up when the component is destroyed, protecting against memory leaks, so you don’t need to unsubscribe from the route paramMap Observable.
Also the example below is a good example from Angular to create a component and destroy it after, look at how component implements OnDestroy, if you need onInit, you also can implements it in your component, like implements OnInit, OnDestroy
import { Component, Input, OnDestroy } from '@angular/core'; import { MissionService } from './mission.service'; import { Subscription } from 'rxjs/Subscription'; @Component({ selector: 'my-astronaut', template: ` {{astronaut}}: {{mission}}
` }) export class AstronautComponent implements OnDestroy { @Input() astronaut: string; mission = ''; 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(); } }
Following the answer by @seangwright , I’ve written an abstract class that handles “infinite” observables’ subscriptions in components:
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 = new Subject(); constructor() {} subscribe(observable: Observable ): Subscription; subscribe( observable: Observable , observer: PartialObserver ): Subscription; subscribe( observable: Observable , next?: (value: any) => void, error?: (error: any) => void, complete?: () => void ): Subscription; subscribe(observable: Observable , ...subscribeArgs): Subscription { return observable .takeUntil(this.onDestroySource) .subscribe(...subscribeArgs); } ngOnDestroy() { this.onDestroySource.next(); this.onDestroySource.complete(); } }
To use it, just extend it in your angular component and call the subscribe()
method as follows:
this.subscribe(someObservable, data => doSomething());
It also accepts the error and complete callbacks as usual, an observer object, or not callbacks at all. Remember to call super.ngOnDestroy()
if you are also implementing that method in the child component.
Find here an additional reference by Ben Lesh: RxJS: Don’t Unsubscribe .
Another short addition to the above mentioned situations is: