Angular- How to avoid Memory Leaks from Subscriptions?

Angular- How to avoid Memory Leaks from Subscriptions?

In Angular 2+, RxJs is used extensively and as developers we should be careful about the performance drawbacks of excessive subscriptions. The Angular team, when integrating rxjs into the platform, were well aware of this.

Problem

Lets say you have two routes in your website — /home and /product. Both show the homeComponent and the productComponent respectively. Lets assume that both the components are subscribing to a some Observable. We don’t care what Observable it is. When the user navigates to the product page from the home page, the homeComponent is destroyed and the productComponent initialises. The homeComponent was subscribed to the Observable and now its destroyed. We did not unsubscribe before destroying the component.

Failing to Unsubscribe can cause memory leaks.

Solution

There are many solutions on to this problem. But the most elegant and error-proof solution is as follows:

import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subject } from "rxjs/Subject";
import { Observable } from "rxjs/Observable";

import { GameService, GameGroupService } from '../../../shared/services';

@Component({
    selector: 'group-games-page',
    templateUrl: './group-games.component.html',
    styleUrls: ['./group-games.component.css'],
    moduleId: module.id
})

export class GroupGamesComponent implements OnInit, OnDestroy {

    public groupGames: Observable;
    public brandId;
   
    private ngUnsubscribe: Subject = new Subject();

    constructor() { }

    ngOnInit(): any {
        this.getGroupGamesByService();
        this.getBrandId();
    }

   
    getGroupGamesByService() {
        this.gameGroupService.getCurrentGameGroupId()
            .takeUntil(this.ngUnsubscribe)
            .subscribe((groupId) => {
                this.groupGames = this.gameService.getGamesById(groupId);
            });
    }

    getBrandId() {
        this.gameGroupService.getCurrentBrandId()
            .takeUntil(this.ngUnsubscribe)
            .subscribe((brandId) => {
                this.brandId = brandId;
            });
    }

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

Here we have a GroupGames component. It uses two services. We don’t care what the services do in this example. Here we are focusing on three things:

  1. Defining a private variable ngUnsubscribe of type Subject.

  2. Using the takeUntil method on the Observable returned from Service methods such as getCurrentGameGroupId and getCurrentBrandId . This method takes in the ngSubscribe variable.

  3. The ngOnDestroy method completes the Subject.

This is basically saying that continue the subscription to these methods as long as ngUnsubscribe is not complete. We make ngUnsubscribe complete in the ngOnDestroy method. This way is simple and intuitive. And you can use the takeUntil method in a component anywhere there is a subscription. Whenever the component is destroyed it will automatically unsubscribe from ALL the subscriptions.

Alternatives

In the beginning of the article I mentioned that the Angular team were well aware of the performance issues regarding excessive subscriptions. Since one of the core values of the Angular team is to help the developers while developing, they gave us an amazing feature called the async pipe. This is a pipe that you can use in your html templates. In short, it automatically subscribes to the Observable passed into the pipe. It unwraps the Observable and gives us the latest unwrapped data in our html. Upon destruction of the component that this html template is attached to, the async pipe automatically unsubscribes to the subscription. This way, you don’t have to unsubscribe manually and this is the preferred way in Angular. This Article gives a good introduction to the async pipe and I recommend reading it.

Verdict

Sometimes you can not use the async pipe due to some constraints, in which case you need to remember to unsubscribe using the method in this article. When such constraints present themselves, you should always question if it is a design fault, or is there really no way you can use the async pipe. In MOST situations, you should be using the async pipe method.

Hope this article helped you making you avoid memory leaks. Cheers!

2019-05-16