Wednesday, 17 May, 2017 UTC


Summary

Angular takes care of unsubscribing from many observable subscriptions like those returned from the Http service or when using the async pipe. Outside of that however, it can quickly become a mess to keep tabs on all subscriptions and make sure we unsubscribe from those that are long lived. Or, we can play it safe and unsubscribe from pretty much everything, which gets even more tedious. Luckily for us, we can use the power of RxJS and the takeUntil operator to declaratively manage subscriptions.
The following applies to Angular 2+ apps.
Unsubscribing Manually
Let’s start with a basic example where we’ll manually unsubscribe from two subscriptions. In this example we’ll subscribe to an Apollo watchQuery to get data from a GraphQL endpoint, and we’ll also create an interval observable that we subscribe to when an onStartInterval method gets called:
import { Component, OnInit, OnDestroy } from '@angular/core'; import { Observable } from 'rxjs/Observable'; import { Subscription } from 'rxjs/Subscription'; import 'rxjs/add/observable/interval'; import { Apollo } from 'apollo-angular'; import gql from 'graphql-tag'; @Component({ ... }) export class AppComponent implements OnInit, OnDestroy { myQuerySub: Subscription; myIntervalSub: Subscription; myQueryObs: Observable<any>; constructor(private apollo: Apollo) {} ngOnInit() { this.myQueryObs = this.apollo.watchQuery({ query: gql` query getAllPosts { allPosts { title description publishedAt } } ` }); this.myQuerySub = this.myQueryObs.subscribe(({data}) => { console.log(data); }); } onStartInterval() { this.myIntervalSub = Observable.interval(250).subscribe(val => { console.log('Current value:', val); }); } ngOnDestroy() { this.myQuerySub.unsubscribe(); if (this.myIntervalSub) { this.myIntervalSub.unsubscribe(); } } }
Now imagine that your component has many similar subscriptions, it can quickly become quite a process to ensure everything gets unsubscribed when the component is destroyed.
Unsubscribing Declaratively with takeUntil
The solution is to compose our subscriptions with the takeUntil operator and use a subject that emits a truthy value in the ngOnDestroy lifecycle hook.
The following snippet does the exact same thing, but this time we unsubscribe declaratively. You’ll notice that an added benefit is that we don’t need to keep references to our subscriptions anymore:
import { Component, OnInit, OnDestroy } from '@angular/core'; import { Observable } from 'rxjs/Observable'; import { Subject } from 'rxjs/Subject'; import 'rxjs/add/observable/interval'; import 'rxjs/add/operator/takeUntil'; import { Apollo } from 'apollo-angular'; import gql from 'graphql-tag'; @Component({ ... }) export class AppComponent implements OnInit, OnDestroy { destroy$: Subject<boolean> = new Subject<boolean>(); constructor(private apollo: Apollo) {} ngOnInit() { this.apollo.watchQuery({ query: gql` query getAllPosts { allPosts { title description publishedAt } } ` }) .takeUntil(this.destroy$) .subscribe(({data}) => { console.log(data); }); } onStartInterval() { Observable .interval(250) .takeUntil(this.destroy$) .subscribe(val => { console.log('Current value:', val); }); } ngOnDestroy() { this.destroy$.next(true); // Now let's also unsubscribe from the subject itself: this.destroy$.unsubscribe(); } }
🎩 Done! Now you magically unsubscribe from everything!