The easiest way to unsubscribe from Observables in Angular

Since version 2, Angular heavily relies on ReactiveX for its API. The Http service returns Observables instead of Promises (as in version 1), route parameters are queried as Observables and EventEmitter is basically an alias for Subject.

While this enables much richer service compositions and helps to arrange complex data flows, it puts an additional burden on us developers: We always need to correctly unsubscribe our Subscriptions. Here, “correctly” mostly means at the right time. If we miss to do so, we create memory leaks that are very, very hard to debug. Also, depending on the code in our subscription, we simply create bugs.

The Problem

export class SimpleComponent implements OnInit, OnDestroy {
  private paramsSubscription: Subscription;
private httpSubscription: Subscription;
  constructor(private route: ActivatedRoute,
private http: Http) {
}
  ngOnInit() {
this.paramsSubscription = this.route.params
.subscribe(params => {
// do something
});
this.httpSubscription = this.http.get("/load")
.subscribe(result => {
// do something
});
}
  ngOnDestroy() {
this.paramsSubscription.unsubscribe();
this.httpSubscription.unsubscribe();
}
}

For every Observable.subscribe(), we store the Subscription instance and call its unsubscribe method in the ngOnDestroy callback. Angular calls the ngOnDestroy method once the component is not used anymore. Therefore, its the perfect place to end our subscriptions. While this solution might be OK for if you have one or two subscriptions, it becomes very tedious if you have more subscriptions.

If you already use Angular for while, I bet you’ve searched the web for a better solutions. The most common solutions are:

  1. Combine subscriptions with subscription.add(). This way you only need to call one unsubscribe().
  2. Use a dummy Subject the emits a value in ngOnDestroy and use takeUntil(subject) with your Observables.
  3. I’ve seen an “interesting” blog article that uses Decorators and string constants to reference instance members.

In my opinion, the best architecture is 2). Hence you need to

  • create an instance member: private onDestroy$ = new Subject();
  • use this subject with your Observables: this.http.takeUntil(this.onDestroy$).subscribe(…)
  • emit a value in the ngOnDestroy callback: this.onDestroy$.next();

However, I think it is annoying to create the subject and call next() in almost every Angular component. It is so fundamental that I think it shouldn’t be necessary.

The solution

Maybe because TypeScript is a statically typed language, developers often forget that it is still based on JavaScript and that it is very easy to alter objects at runtime. Hence, we can completely automate the subject creation and the method call next():

export class SimpleComponent implements OnInit, OnDestroy {
constructor(private route: ActivatedRoute,
private http: Http) {
}
ngOnInit() {
this.route.params
.takeUntil(componentDestroyed(this))
.subscribe(params => {
// do something
});
    this.http.get("/load")
.takeUntil(componentDestroyed(this))
.subscribe(result => {
// do something
});
}
ngOnDestroy() {
// empty
}
}

We still use the takeUntil operator. But instead of passing in a custom Subject, we only call componentDestroyed(this). No need to create a Subject and manually call next() in ngOnDestroy. At runtime, the function componentDestroyed alters the component instance and creates a new ngOnDestroy method which in turn calls an internally created Subject. The existing ngOnDestroy gets called by the new ngOnDestroy method. This way, we can call componentDestroyed as often as needed. Unfortunately, Angular only calls the ngOnDestroy method if it already existed at the time the component was instantiated. Therefore, we need at least an empty ngOnDestroy method. However, the componentDestroy function has a “component: OnDestroy” parameter. Hence, the TypeScript compiler throws an error if you missed to add this method.

Download

The componentDestroyed method is available as NPM package:

https://www.npmjs.com/package/@w11k/ngx-componentdestroyed