When should I store the Subscription
instances and invoke unsubscribe()
during the NgOnDestroy life cycle and when can I simply ignore them?
I like the last two answers, but I experienced an issue if the the subclass referenced "this"
in ngOnDestroy
.
I modified it to be this, and it looks like it resolved that issue.
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() {}
}
You don't need to have bunch of subscriptions and unsubscribe manually. Use Subject and takeUntil combo to handle subscriptions like a boss:
import { Subject } from "rxjs"
import { takeUntil } from "rxjs/operators"
@Component({
moduleId: __moduleName,
selector: "my-view",
templateUrl: "../views/view-route.view.html"
})
export class ViewRouteComponent implements OnInit, OnDestroy {
componentDestroyed$: Subject<boolean> = new Subject()
constructor(private titleService: TitleService) {}
ngOnInit() {
this.titleService.emitter1$
.pipe(takeUntil(this.componentDestroyed$))
.subscribe((data: any) => { /* ... do something 1 */ })
this.titleService.emitter2$
.pipe(takeUntil(this.componentDestroyed$))
.subscribe((data: any) => { /* ... do something 2 */ })
//...
this.titleService.emitterN$
.pipe(takeUntil(this.componentDestroyed$))
.subscribe((data: any) => { /* ... do something N */ })
}
ngOnDestroy() {
this.componentDestroyed$.next(true)
this.componentDestroyed$.complete()
}
}
Alternative approach, which was proposed by @acumartini in comments, uses takeWhile instead of takeUntil. You may prefer it, but mind that this way your Observable execution will not be cancelled on ngDestroy of your component (e.g. when you make time consuming calculations or wait for data from server). Method, which is based on takeUntil, doesn't have this drawback and leads to immediate cancellation of request. Thanks to @AlexChe for detailed explanation in comments.
So here is the code:
@Component({
moduleId: __moduleName,
selector: "my-view",
templateUrl: "../views/view-route.view.html"
})
export class ViewRouteComponent implements OnInit, OnDestroy {
alive: boolean = true
constructor(private titleService: TitleService) {}
ngOnInit() {
this.titleService.emitter1$
.pipe(takeWhile(() => this.alive))
.subscribe((data: any) => { /* ... do something 1 */ })
this.titleService.emitter2$
.pipe(takeWhile(() => this.alive))
.subscribe((data: any) => { /* ... do something 2 */ })
// ...
this.titleService.emitterN$
.pipe(takeWhile(() => this.alive))
.subscribe((data: any) => { /* ... do something N */ })
}
ngOnDestroy() {
this.alive = false
}
}
Since seangwright's solution (Edit 3) appears to be very useful, I also found it a pain to pack this feature into base component, and hint other project teammates to remember to call super() on ngOnDestroy to activate this feature.
This answer provide a way to set free from super call, and make "componentDestroyed$" a core of base component.
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() {}
}
And then you can use this feature freely for example:
@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');
}
}
You can use latest Subscription class to unsubscribe for the Observable with not so messy code.
We can do this with normal variable
but it will be override the last subscription
on every new subscribe so avoid that, and this approach is very much useful when you are dealing with more number of Obseravables, and type of Obeservables like BehavoiurSubject and Subject
Subscription
Represents a disposable resource, such as the execution of an Observable. A Subscription has one important method, unsubscribe, that takes no argument and just disposes the resource held by the subscription.
you can use this in two ways,
you can directly push the subscription to Subscription Array
subscriptions:Subscription[] = [];
ngOnInit(): void {
this.subscription.push(this.dataService.getMessageTracker().subscribe((param: any) => {
//...
}));
this.subscription.push(this.dataService.getFileTracker().subscribe((param: any) => {
//...
}));
}
ngOnDestroy(){
// prevent memory leak when component destroyed
this.subscriptions.forEach(s => s.unsubscribe());
}
using add()
of Subscription
subscriptions = new Subscription();
this.subscriptions.add(subscribeOne);
this.subscriptions.add(subscribeTwo);
ngOnDestroy() {
this.subscriptions.unsubscribe();
}
A Subscription
can hold child subscriptions and safely unsubscribe them all. This method handles possible errors (e.g. if any child subscriptions are null).
Hope this helps.. :)
The SubSink package, an easy and consistent solution for unsubscribing
As nobody else has mentioned it, I want to recommend the Subsink package created by Ward Bell: https://github.com/wardbell/subsink#readme.
I have been using it on a project were we are several developers all using it. It helps a lot to have a consistent way that works in every situation.
--- Update Angular 9 and Rxjs 6 Solution
unsubscribe
at ngDestroy
lifecycle of Angular Componentclass SampleComponent implements OnInit, OnDestroy {
private subscriptions: Subscription;
private sampleObservable$: Observable<any>;
constructor () {}
ngOnInit(){
this.subscriptions = this.sampleObservable$.subscribe( ... );
}
ngOnDestroy() {
this.subscriptions.unsubscribe();
}
}
takeUntil
in Rxjsclass SampleComponent implements OnInit, OnDestroy {
private unsubscribe$: new Subject<void>;
private sampleObservable$: Observable<any>;
constructor () {}
ngOnInit(){
this.subscriptions = this.sampleObservable$
.pipe(takeUntil(this.unsubscribe$))
.subscribe( ... );
}
ngOnDestroy() {
this.unsubscribe$.next();
this.unsubscribe$.complete();
}
}
ngOnInit
that just happen only one time when component init.class SampleComponent implements OnInit {
private sampleObservable$: Observable<any>;
constructor () {}
ngOnInit(){
this.subscriptions = this.sampleObservable$
.pipe(take(1))
.subscribe( ... );
}
}
We also have async
pipe. But, this one use on the template (not in Angular component).