Angular: Managing RxJS Observable Subscriptions with UntilDestroy

untildestroy

Unsubscribing from the Subscription: What’s The Big Deal?

Whether you’re listening for events from the Router, listening for changes in a FormControl, or making a request to an API with their HttpClient, if you’ve developed in Angular, you’ve interacted with or subscribed to an RxJS Observable. But do you know why and when you’re responsible for unsubscribing from the Subscription?

Why We Unsubscribe

The goal of unsubscribing is to prevent memory leaks.  When debugging code, memory leaks are subtle and easily missed.  In Angular’s case, it also requires a bit of extra knowledge to know when you’re responsible for the Subscription and when Angular is.

When Angular Handles The Subscription

Angular in some cases is nice enough to handle unsubscribing, for example, if you’re making an API call using their HttpClient:

httpClient.get("/v1/heroes").subscribe((heroes:IHero[])=>{});

If you’re using the async Pipe in your HTML:

<app-heroes-list [heroes]="heroes$ | async"></app-heroes-list>

Angular will handle it. But there are several times you’re responsible for unsubscribing.

When The Developer Handles The Subscription

As a developer, you’re responsible for a Subscription in several cases:

  • Subscribing to events from the Router
  • Subscribing to valueChanges from an AbstractControl (ex. ReactiveForms FormGroup, FormArray, FormControl)
  • RxJS Observables and long-lived Operators (ex. Subjects, timer, interval, etc.)

And in these cases, you’ll see a few approaches that developers use to manage them.

Approach One: Assignment and ngOnDestroy

The most basic approach you can take is to store the Subscription and then unsubscribe from it when a Component’s ngOnDestroy lifecycle method is invoked.

@Component(...)
export class HeroesComponent implements OnInit, OnDestroy {

  private mySubscription: Subscription;

  constructor() { }

  private ngOnInit():void {

    this.mySubscription = interval(1000).subscribe(() => { 
         // Do something here. 
    });

  }

  private ngOnDestroy():void {

   this.mySubscription.unsubscribe();

  }
}

Feels like a lot of work. In classes where multiple subscriptions are being made, you could have more variables to hold them or change your mySubscription:Subscription into an array but that doesn’t make for an elegant approach.

Approach Two: RxJS takeUntil()

The takeUntil operator helps us get a bit further in making a clean break with our Subscription. Passing it in as an operator will allow it to mirror the source Observable until a notifier emits.

Once the notifier emits, the Observable stops mirroring and completes. Let’s update our previous example:

@Component(...)
export class HeroesComponent implements OnInit, OnDestroy {

  private notifier$:Subject<boolean> = new Subject<boolean>();

  constructor() { }

  private ngOnInit():void {

    interval(1000).pipe(takeUntil(this.notifier$)).subscribe(() => { 
         // Do something here. 
    });

  }

  private ngOnDestroy():void {

   this.notifier$.next(true);
   this.notifier$.complete();

  }
}

This approach is a bit cleaner, we avoid tracking any number of subscriptions but still have a bit more code just around the management of the notifier. With the next approach, we can make this even easier.

Approach Three: ngneat/until-destroy(Until Destroy) 

If you haven’t come across this operator yet, you definitely need to check out this repo. It’s called ngneat/until-destroy (GitHub Repo) and it’s going to help us clean up our code. In their latest version (as of this article), they provide a decorator for your Component and an operator. Let’s update our previous example one more time:

@UntilDestroy()
@Component(...)
export class HeroesComponent implements OnInit {
 
  constructor() { }

  private ngOnInit():void {

    interval(1000).pipe(untilDestroyed(this)).subscribe(() => { 
         // Do something here. 
    });

  }
}

Note the placement and order of the @UntilDestroy decorator and the argument we pass into the operator. Now we no longer need any class-level variables or code in the ngOnDestroy to handle our Subscription. That’s it!

UntilDestroy:  Until Next Time

Having worked with Angular and RxJS for years, the ngneat/until-destroy library was a breath of fresh air. It’s definitely my preferred approach to Subscription management and hopefully, you’ll find it useful too! If you do have any other questions about his series of code, stay tuned for more random finds in the Angular world, or if you would like to speak directly to a Ronin, contact us today! Our team has been working in Angular for years, and can answer any questions you may have! 

Author:
Brian Weiss is a UI/UX Architect hellbent on designing and developing, meaningful, human-focused applications. He's also hellbent on finding the best chocolate chip cookie this world has to offer. When he's not developing software or eating cookies, he's probably watching anime and collecting Funko Pops.