How to Unsubscribe from observables in Angular (many ways)

digitaldino

Dino Dujmovic

Posted on April 14, 2023

How to Unsubscribe from observables in Angular (many ways)

There are many posts and videos out there discussing how to unsubscribe from Observables in Angular, so I was thinking - why shouldn't I write another one?

Introduction

Here are some important things to keep in mind when working with Observables in Angular:

  • In order to retrieve the value from an Observable, we need to subscribe to it.
  • Unsubscription does not happen automatically. We are responsible for unsubscribing from the Observable when we no longer need it.
  • We need to unsubscribe from our Observables because if we don't, they can continue to emit values even after the component is no longer present in the DOM (i.e., destroyed). This can cause memory leaks, as the subscription and associated resources will still be active in memory, even though they are no longer needed.

Async Pipe

The most safe way to approach subscribing and rendering the data in Angular is using async pipe.

When we use the async pipe in our template to bind to an Observable, Angular takes care of subscribing to and unsubscribing from the Observable for us. This means that we don't need to manually subscribe to the Observable in our component code, and we don't need to worry about manually unsubscribing and it also simplifies the code quite a bit.

Note: For examples below MovieService makes HTTP call with Angular's HttpClient module that returns observable

@Component({ 
  template: `
    <div class="movies">
       <div *ngIf="popularMovies$ | async as movies" class="popular-movies">
          <div *ngFor="let movie of movies">{{ movie.title }} </div>
       </div>

       <div *ngIf="trendingMovies$ | async as movies" class="trending-movies">
          <div *ngFor="let movie of movies">{{ movie.title }} </div>
       </div>
    </div>
  `
})
class MovieComponent implements OnInit, OnDestroy {
    popularMovies$: Observable<IMovie[]>;
    trendingMovies$: Observable<IMovie[]>;

    constructor(private movieService: MovieService) { 
       this.popularMovies$ = movieService.getPopularMovies()
       this.trendingMovies$ = movieService.getTrendingMovies()
    }
}
Enter fullscreen mode Exit fullscreen mode

Given the benefits that it provides, I believe that the async pipe should be used wherever possible in Angular applications, but there may be situations where you would need to subscribe/unsubscribe manually and here is how:


1) Basic approach using unsubscribe

@Component({...})
class MovieComponent implements OnInit, OnDestroy {
    popularMoviesSub: Subscription;
    trendingMoviesSub: Subscription;

    constructor(private movieService: MovieService) {
        this.popularMoviesSub = movieService.getPopularMovies().subscribe();
        this.trendingMoviesSub = movieService.getTrendingMovies().subscribe();
    }

    ngOnDestroy() { 
       this.popularMoviesSub.unsubscribe()
       this.trendingMoviesSub.unsubscribe()
    }
}
Enter fullscreen mode Exit fullscreen mode

The problem with this approach is that we could be creating many Subscription properties and we may also easily forget to unsubscribe from all of them.

2) RxJS takeUntil

@Component({...})
export class MovieComponent implements OnInit, OnDestroy {
    destroy$ = new Subject<boolean>();

  constructor(private movieService: MovieService) {
       movieService.getPopularMovies()
            .pipe(takeUntil(this.destroy$))
            .subscribe()
       movieService.getTrendingMovies()
                   .pipe(takeUntil(this.destroy$))
                   .subscribe()
    }

    ngOnDestroy() {
        this.destroy$.next();
    }
}
Enter fullscreen mode Exit fullscreen mode

This is considered to be the most correct reactive programming approach.

However, in my humble opinion it can result in code that is less aesthetically pleasing and harder to understand for many.

3) Simple solution I like to use

My personal belief is that simplicity and readability are critical to writing high-quality code. It's important to strive for code that is clear, concise, and easy to follow, regardless of whether the intended audience is a beginner or an advanced developer.

@Component({...})
export class MovieComponent implements OnInit, OnDestroy {
    private subs = new ManualSubs();

    constructor(private movieService: MovieService) {
       this.subs.add = movieService.getPopularMovies().subscribe();
       this.subs.add = movieService.getTrendingMovies().subscribe();
    }

    ngOnDestroy() {
        this.subs.unsubscribe();
    }
}
Enter fullscreen mode Exit fullscreen mode

Here is how ManualSubs class looks like:

// manual-subs.ts

export class ManualSubs {
    private subs: Subscription[] = [];

    private isValidSubscription(sub: Subscription) {
        return sub && typeof sub.unsubscribe === "function";
    }

    set add(sub: Subscription) {
        if (this.isValidSubscription(sub)) {
            this.subs.push(sub);
        }
    }

    public unsubscribe() {
        this.subs.forEach((sub: Subscription) => this.isValidSubscription(sub) && sub.unsubscribe());
        this.subs = [];
    }
}
Enter fullscreen mode Exit fullscreen mode

Never forget to test!

// manual-subs.spec.ts

describe("ManualSubs", () => {
    let manualSubs: ManualSubs;
    let sub1: Subscription;
    let sub2: Subscription;

    beforeEach(() => {
        manualSubs = new ManualSubs();
        sub1 = jasmine.createSpyObj("Subscription", ["unsubscribe"]);
        sub2 = jasmine.createSpyObj("Subscription", ["unsubscribe"]);
    });

    it("should unsubscribe from all subscriptions", () => {
        manualSubs.add = sub1;
        manualSubs.add = sub2;
        manualSubs.unsubscribe();
        expect(sub1.unsubscribe).toHaveBeenCalledTimes(1);
        expect(sub2.unsubscribe).toHaveBeenCalledTimes(1);
    });
});
Enter fullscreen mode Exit fullscreen mode

Existing libraries using this approach:

However, it's worth considering whether you truly need an external npm module for a relatively small amount of code, especially when you could easily write, manage, expand, and use your own naming conventions.


Summary

The approaches discussed are some of the most straightforward and commonly used methods for managing subscriptions.
However, there are also other more "advanced" techniques available that can simplify subscription management and reduce boilerplate code, such as using Angular decorators.

As always we can implement those ourselves, but there are some nice solutions out there if some would prefer.

💖 💪 🙅 🚩
digitaldino
Dino Dujmovic

Posted on April 14, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related