background
In reactive world(rxjs/ng2+), it is common and convenient to just create some subject/observable and subscribe to them for event handling etc. It is like the gof observer pattern
out of the box.
issue
One caveat we recently have is, we call subscribe()
of some subjects from our service in our ngOnInit
or ngAfterViewInit
funtions we forget to unsubscribe the subscriptions in our component. The consequence is each time the component is recreated during route change, one more subscription will be added to the subject
, this is pretty bad if we are doing something heavy in the callback or even worse making some http call.
solution 1 – unsubscribe in ngOnDestroy
One solution is to call keep a reference of the subscription
which is return by the subscribe function and then call its unsubscribe()
function in the angular’s ngOnDestroy()
lifecycle hook. It would work and is fine if there are only a few of them. If there are many and need to be called on each related component, it would be quite tedious.
Solution 2 – custom decorator calling ngOnDestroy
Another solution is to write a custom decorator which will provide logic for ngOnDestory. And the component itself still need to keep a list of subscriptions.
Solution 3 – use takeUntil operator
This way is to use a global subject to tell all subscription to stop taking values once it emit a value. It is more declarative IMHO.
import { OnDestroy } from '@angular/core'; import { Subject } from 'rxjs/Subject'; /** * extend this class if component has subscription need to be unsubscribed on destroy. * * example: myObservable.takeUntil(this.destroyed$).subscribe(...); */ export abstract class UnsubscribableComponent implements OnDestroy { // the subject used to notify end subscription(usually with `takeUntil` operator). protected destroyed$: Subject = new Subject(); protected constructor() {} ngOnDestroy(): void { this.destroyed$.next(true); this.destroyed$.complete(); } }
So in the component it can be something like:
export class MyOwnComponent extends UnsubscribableComponent implements OnInit { ngOnInit() { this._eventsContainerService.allEventsInfo .takeUntil(this.destroyed$) .subscribe(result => { if (result) { this.handleAllEventsLoad(result); } }); }