40

Unsubscribe from RxJS Observables to avoid memory leak

Wednesday, October 23, 2019

RxJS is the incredible powerful reactive extensions for JavaScript library, but as Peter Parker's uncle Ben says: "With great power comes great responsibility".

Our responsibility when subscribing to an observable stream is to be sure that we unsubscribe. We unsubscribe when we not longer need additional (next, complete or error) notifications. In a component based framework such as React or Angular it is most common to unsubscribe from a subscription when the component is unmounted or destroyed. Both React and Angular provide a mechanism, or life-cycle method, for cleaning things up when a component is unmounted or destroyed.

If we fail to unsubscribe we could have a possible memory leak in our application. The memory leak is caused by unnecessary subscriptions.

It is important to understand that the signature for the subscribe() method in the Observable class is:

subscribe(observerOrNext?: PartialObserver<T> | ((value: T) => void), error?: (error: any) => void, complete?: () => void): Subscription

There is a lot of type declarations in the above signature. For now, let's focus on the return type: Subscription. This informs us that when we invoke the subscribe() method that we receive a new Subscription instance.

The Subscription class enables us to manage a subscription. Let's have a quick look at the Subscription class:

class Subscription implements SubscriptionLike {
  static EMPTY: Subscription
  constructor(unsubscribe?: () => void)
  closed: [object Object]
  unsubscribe(): void
  add(teardown: TeardownLogic): Subscription
  remove(subscription: Subscription): void
}

Take note of the following methods:

  • unsubscribe() will unsubscribe this subscription, along with all child subscriptions
  • add() will add a child subscription to this subscription

Using these methods we can unsubscribe from RxJS observables to avoid a memory leak.

Instructions

Do

Use Subscription.add() to add child subscriptions

Invoke Subscription.unsubscribe(), often when unmounting or destroying a component

Avoid

The takeUntil() operator approach.

Why

Similar to event listeners, open subscriptions that are unecessary leads to memory leaks in your application

Code Examples

Foo

import { Component, OnInit } from '@angular/core';
import { MatDialog } from '@angular/material';
import { Rule } from '@lkt-core/models';
import { ConfirmationDialogComponent, ConfirmationDialogData } from '@lkt-shared/components';
import { RuleFacade } from '@lkt-state/rule';
import { Observable } from 'rxjs';

@Component({
  templateUrl: './index.component.html',
  styleUrls: ['./index.component.scss']
})
export class IndexComponent implements OnInit {
  /** Toggle loading indicator. */
  loading: Observable<boolean>;

  /** The rules to display. */
  rules: Observable<Array<Rule>>;

  constructor(private readonly matDialog: MatDialog, private readonly ruleFacade: RuleFacade) {}

  ngOnInit() {
    this.loading = this.ruleFacade.loading;
    this.rules = this.ruleFacade.loadAll();
  }

  onRemove(rule: Rule): void {
    const data: ConfirmationDialogData = { title: 'Remove Rule' };
    const dialogRef = this.matDialog.open(ConfirmationDialogComponent, {
      data
    });
    dialogRef.afterClosed().subscribe(value => value && this.ruleFacade.remove(rule));
  }
}
Brian Love

I am a software engineer and Google Developer Expert in Web Technologies and Angular with a passion for learning, writing, speaking, teaching and mentoring. I regularly speaks at conferences and meetups around the country, and co-authored "Why Angular for the Enterprise" for O'Reilly. When not coding, I enjoy skiing, hiking, and being in the outdoors. I started lookout.dev to break down the barriers of learning in public. Learning in public fosters growth - for ourselves and others.

Google Developers Expert

Discussions are healthy ❤️