RxJS Observable – Angular Best Practice: Unsubscribing

by | Feb 6, 2019

One of the things that Angular developers should know how to do properly is unsubscribing from RxJS Observable subscriptions.  They should also know when to unsubscribe since sometimes it’s done for you and isn’t necessary.  This article will dive into these topics.  It is mainly going to draw from this stack overflow discussion on the topic.  Go there for a really deep dive into this topic.  This article gets into the main points.

 

Types of Subscriptions

RxJS subscriptions are done quite often in Angular code.  For example, when calling an API that returns an RxJS Observable or listening for changes in an RxJS Observable like a DOM event listener.  We want to make sure we don’t keep listening to RxJS Observables after the component is gone so that’s why we need to unsubscribe.

 

Finite Subscriptions

One thing a rookie Angular developer will struggle with is making an API call like this and have it do nothing:

this.http.get<string>('your/url');

The reason it does nothing is that it must be subscribed to (see here for a deeper explanation but the gist is:  calling subscribe is actually the moment when Observable starts its work.)

this.http.get<string>('your/url').subscribe(() => { });

Now the http get call works because of the subscribe.  However, it is totally unnecessary to unsubscribe from HttpClient method calls since it limits the RxJS Observable to execute once.  These are called “finite subscriptions”.  If you subscribe with a “take(1)”, as another example, it is finite and doesn’t need to be unsubscribed.  This is easy enough to test out if you are unsure of it being finite or infinite.  For example, you could console.log information within your subscription and then have code that changes the value so the subscription is entered.  Exit the component multiple times and try again to change the value, see what the console.log is doing.  If it doesn’t keep logging, you are fine.

 

Infinite Subscriptions

The other really common subscription is for Observables that you created in a service or in NgRx selectors.  The typical scenario would be if you have a service that is used on multiple components and you want to know when something changes on one component so you can change it in another one.

This would look something like this:

this.someService.MySubject.subscribe((isValid) => if (isValid) {} else {});

This type of subscription is the type you must unsubscribe from because Angular/RxJS has no automatic way of knowing when you don’t want to listen for it any longer.  After all, you created it.  Another type would be the common scenario where you are subscribing to DOM input events so you can do “debounceTime” filtering to limit the number of times you call an API to get type-ahead lists.  

There is at least one caveat to this type of subscription.  If you have HTML code that is using the “async” pipe, it gets subscribed and unsubscribed automatically.

For example, you would have this piece of HTML:

<div>{{ title$ | async }}</div>

Then in the TypeScript code:

title$: Subject<string>;

ngOnInit() {
  this.title$ = this.someService.Title;  // Title is Subject<string> or BehaviorSubject<string>, maybe it changes for different languages
}

Note that you don’t even have to subscribe for this to work.  The async pipe does that for you as well as unsubscribing.  This type of code is common in NgRx solutions as well.  In place of the this.someService.Title code, you would instead have a selector, something like:  this.title$ = this.store.select(mySelector);

 

Problematic Solutions to Unsubscribing

There are definitely less than desirable ways to unsubscribe.  This section will list three of them.

 

Storing Subscriptions in Variables

The most common way of handling unsubscribing is to store your subscriptions in a component variable.  This is fine for a hello world app but once you start getting a lot of them on one component, it starts to not scale very well.  It’s a pain, really.  Consider this code:

let sub1: Subscription;
let sub2: Subscription;
let sub3: Subscription;

ngOnInit() {
  this.sub1 = this.service.Subject1.subscribe(() => {});
  this.sub2 = this.service.Subject2.subscribe(() => {});
  this.sub3 = this.service.Subject3.subscribe(() => {});
}

ngOnDestroy() {
  if (this.sub1 != null) {
    this.sub1.unsubscribe();
  }
  if (this.sub2 != null) {
    this.sub2.unsubscribe();
  }
  if (this.sub3 != null) {
    this.sub3.unsubscribe();
  }
}

That was painful just to type out for this article.  I’ll pass on that.

 

Storing Subscriptions in an Array

Another thing I’ve seen is storing the subscriptions in a Subscription array and then unsubscribing using “forEach” in the destroy.

let subs: Subscription[] = [];

ngOnInit() {
  this.subs.push(this.service.Subject1.subscribe(() => {}));
  this.subs.push(this.service.Subject2.subscribe(() => {}));
  this.subs.push(this.service.Subject3.subscribe(() => {}));
}

ngOnDestroy() {
  subs.forEach(sub => sub.unsubscribe());
}

This is a little better but still, not very nice since you are still managing the subscriptions.

 

Using Subscription.add Method

Another alternative to the array method is to use the Subscription “add” method to store all of the subscriptions and then just do one unsubscribe to get them all in destroy.

private subscriptions = new Subscription();

ngOnInit() {
  this.subscriptions.add(this.service.Subject1.subscribe(() => {}));
  this.subscriptions.add(this.service.Subject2.subscribe(() => {}));
  this.subscriptions.add(this.service.Subject3.subscribe(() => {}));
}

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

This isn’t horrible and is probably better than the array version but you still have to manage the subscriptions.

 

Best Practice For Unsubscribing

The best practice way of unsubscribing from Observable.subscribe() calls is to use “takeUntil()” in the pipe before your “subscribe”.  It’s best to show with an example and then discuss why it is a best practice.

The following is a base class that I use in my projects to facilitate this best practice:

export class BaseComponent implements OnDestroy {
  ngUnsubscribe = new Subject<void>();

  ngOnDestroy(): void {
    this.ngUnsubscribe.next();
    this.ngUnsubscribe.complete();
  }

}

So if I’m in a component that needs a subscribe() call, I would first extend from BaseComponent like this: export class MyComponent extends BaseComponent

And then do this in MyComponent:

ngOnInit() {
  this.service.Subject1.pipe(takeUntil(this.ngUnsubscribe))
    .subscribe(() => {});
  this.service.Subject2.pipe(takeUntil(this.ngUnsubscribe))
    .subscribe(() => {});
  this.service.Subject3.pipe(takeUntil(this.ngUnsubscribe))
    .subscribe(() => {});
}

Note that the ngUnsubscribe.next()/complete() calls in the base class will end the subscription in an RxJS way…and automatically when the component is destroyed.

So why is this a best practice?

  • Less code to write
  • No need to do anything in ngOnDestroy, the BaseComponent handles that
  • No need to keep track of subscriptions
  • Because Ben Lesh and Ward Bell said so 🙂

Important to know:

  • If you have many pipe operators, make sure that takeUntil is the last one (see here for the reason why)
    • In the article, he discusses using a tslint rule to ensure this is always the case

Conclusion

I hope that after reading this, you are excited about unsubscribing in a best practice way.  There may be changes to Angular/RxJS in the future that will make this irrelevant but until then, this is the best way to do it.  I’ve used this method in a number of projects and it works like a charm.  Also, be at peace knowing that you don’t always have to unsubscribe.  Ben Lesh’s article has a nice list of RxJS operators to use to avoid unsubscribing.  I mentioned a few in this article.  Happy coding!