Picture of Brian Love wearing black against a dark wall in Portland, OR.

Brian Love

Angular: Don't forget to unsubscribe()

Don’t forget to unsubscribe from subscriptions in Angular components using the OnDestroy lifecycle hook. We’ll look at:

  1. Why you need to unsubscribe
  2. When you likely should unsubscribe
  3. How you unsubscribe

Updated July 7, 2018

I have updated this post to correctly use the takeUntil() operator over the takeWhile() approach. Using takeWhile() will only stop receiving notifications from the source observable after another notification is emitted. On the other hand, using takeUntil() will stop receiving notifications from the source observable immediately when we invoke next() on the notifier subject.

Updated January 4, 2017

I have updated this post to include the use of the takeWhile() operator as a better strategy for unsubscribing in your Angular components.

Updated September 13, 2017

I have updated this post to include the use of the takeUntil() operator as another strategy for unsubscribing in an Angular component.

Why?

Reactive-Extensions for JavaScript (or RxJS) introduces the concept of Observables to Angular. If you have been using version 1 of Angular then you are likely comfortable using Promises. And, while you might think that an Observable is just like a Promise you might be surprised (as I was) to learn that they are in fact very different.

First, Promises are eager and are executed immediately. Observables are not eager and are only executed when subscribed to.

Second, Promises are asynchronous. Observables can be either synchronous or asynchronous.

Third, Promises are expected to return only a single value (like a function). Observables can return zero, one or more (infinitely) values.

Let me get to the point. A subscription is created when we subscribe() to an observable. And it is important that we unsubscribe from any subscriptions that we create in our Angular components to avoid memory leaks.

AsyncPipe

Before we go any farther; if you are using observable streams via the AsyncPipe then you do not need to worry about unsubscribing. The async pipe will take care of subscribing and unsubscribing for you.

When?

Ok, we covered the why - but, when do we unsubscribe? In most instances the best time to unsubscribe is when your component is destroyed. Angularintroduced the the ngOnDestroy() lifecycle method:

ngOnDestroy(): Cleanup just before Angular destroys the directive/component. Unsubscribe observables and detach event handlers to avoid memory leaks.

How?

There are three common approaches:

  1. Store the subscription and invoke the unsubscribe() method when the component is destroyed.
  2. Use the takeWhile() operator.
  3. Use the takeUntil() operator.

unsubscribe()

For the sake of an example, let’s create a simple Observable stream:

this.counter = new Observable<number>(observer => {
console.log('Subscribed');

let index = -1;
const interval = setInterval(() => {
  index++;
  console.log(`next: ${index}`);
  observer.next(index);
}, 1000);

// teardown
return () => {
  console.log('Teardown');
  clearInterval(interval);
}

First, import the OnDestroy class from @angular/core as well as the ISubscription interface from rxjs:

import { OnDestroy } from '@angular/core';
import { ISubscription } from 'rxjs/Subscription';

Next, we implement the abstract class and declare a variable to store a reference to our subscription:

export class MyComponent implements OnDestroy {

  private subscription: ISubscription;

}

We then subscribe to the counter observable and store a reference to the returned Subscription instance in the subscription property:

this.subscription = this.counter.subscribe(
  (value) => (this.count = value),
  (error) => console.error(error),
  () => console.log('complete'),
);

When the next notifiction is emitted, we simply set the count property in our component to the updated value. If an error occurs we output the error to the console, and we log the string ‘complete’ when the complete notification is emitted and we have unsubscribed from the observable.

When our component is destroyed we invoke the unsubscribe() method on the subscription:

ngOnDestroy() {
  this.subscription.unsubscribe();
}

If we are subscribing to multiple subscriptions we do not need to use a collection to store all of the subscription instances. Rather, a subscription can have child subscriptions. Simply invoke the add() method to add additional child subscriptions. This is helpful, as all child subscriptions will be unsubscibed when we unsubscribe() the parent subscription.

Let’s add an additional subscription:

const subscription = this.counter.subscribe((value) => console.log(value));
this.subscription.add(subscription);

We simply add() the new subscription to our existing subscription object. Then, invoke the unsubscribe() method, just like we did before:

ngOnDestroy() {
  this.subscription.unsubscribe();
}

Now, all child subscriptions, along with their child subscription if any exist, are unsubscribed.

takeWhile()

Another approach to unsubscribing is to use the http://reactivex.io/documentation/operators/takewhile.html operator:

The TakeWhile mirrors the source Observable until such time as some condition you specify becomes false, at which point TakeWhile stops mirroring the source Observable and terminates its own Observable.

First, we need to import the takeWhile() operator:

import 'rxjs/add/operator/takeWhile';

Then, let’s use the takeWhile() operator within the pipe():

export class CounterTakeWhileComponent implements OnInit, OnDestroy {

  count: number;
  counter: Observable<number>;

  private alive = true;

  ngOnInit() {
    console.log('[takeWhile] ngOnInit');

    this.counter = new Observable<number>(observer => {
      console.log('[takeWhile] Subscribed');

      let index = -1;
      const interval = setInterval(() => {
        index++;
        console.log(`[takeWhile] next: ${index}`);
        observer.next(index);
      }, 1000);

      // teardown
      return () => {
        console.log('[takeWhile] Teardown');
        clearInterval(interval);
      }
    });

    this.counter
    .pipe(takeWhile(() => this.alive))
    .subscribe(
      (value) => this.count = value,
      (error) => console.error(error),
      () => console.log('[takeWhile] complete')
    );
  }

  ngOnDestroy() {
    console.log('[takeWhile] ngOnDestory');
    this.alive = false;
  }
}

In the CounterTakeWhile component we use the alive property to indicate if the component is alive, and then we toggle this to false in the component’s ngOnDestroy() method.

The problem with takeWhile()

It’s important to understand that the takeWhile() operator will only stop receiving notifications, and thus unsubscribe from the source observable, when the next notification is emitted. To better explain this, let’s suppose the following:

  1. First, we subscribe to the counter observable.
  2. Second, the value 0 is emitted.
  3. Then, the value 1 is emitted.
  4. The component is destroyed and the ngOnDestroy() method is invoked and we set alive to false.
  5. Then, the value 1 is emitted.
  6. The takeWhile() operator is invoked, and the predicate returns false, therefore the observable is complete and we have unsubscribed from any additional notifications.

Notice above in the sequence that the source observable is complete only after the value 1 is emitted. While we did toggle alive to false, it is not until the next notification that our takeWhile predicate function is executed.

This could be a problem in your application.

Why? Because if another value is not emitted after toggling the alive boolean to false then we will never complete the source observable stream - and we then have a memory leak in our app.

Is there a better way? Yes, use the takeUntil() operator.

takeUntil()

The takeUntil() operator is the preferable method for completing observables.

The TakeUntil subscribes and begins mirroring the source Observable. It also monitors a second Observable that you provide. If this second Observable emits an item or sends a termination notification, the Observable returned by TakeUntil stops mirroring the source Observable and terminates.

Let’s see this in action:

export class CounterTakeUntilComponent implements OnInit, OnDestroy {

  count: number;
  counter: Observable<number>;

  private unsubscribe: Subject<void> = new Subject();

  ngOnInit() {
    console.log('[takeUntil] ngOnInit');

    this.counter = new Observable<number>(observer => {
      console.log('[takeUntil] Subscribed');

      let index = -1;
      const interval = setInterval(() => {
        index++;
        console.log(`[takeUntil] next: ${index}`);
        observer.next(index);
      }, 1000);

      // teardown
      return () => {
        console.log('[takeUntil] Teardown');
        clearInterval(interval);
      }
    });

    this.counter
    .pipe(takeUntil(this.unsubscribe))
    .subscribe(
      (value) => this.count = value,
      (error) => console.error(error),
      () => console.log('[takeUntil] complete')
    );
  }

  ngOnDestroy() {
    console.log('ngOnDestory');
    this.unsubscribe.next();
    this.unsubscribe.complete();
  }
}

As you can see, this approach is very similar to using the takeWhile() operator.

A few things to note:

takeWhile() vs takeUntil()

Take a look at the examples and compare when the complete notification is emitted when using takeWhile() vs. takeUntil():

Which method?

The question you might be asking yourself at this point is:

Which method should I use to unsubscribe?

First, I would recommend that you use the async pipe. Let the Angular framework manage the subscription for you.

If using the async pipe is not a good solution for your needs, then I would recommend using the takeUntil() operator.