Learn to develop in a simplified way

How I handle subscriptions in Angular to avoid memory leaks

Recently I worked on implementing a filter in our application. Whenever the filter was applied, I emitted an event (using subjects). my component subscribed to this subject and makes an API call whenever an emission happens. Up to this point, everything is fine. But the problem is if you navigate to another component and come back to this component. We are adding another subscription at the initialization of the component and the old one is still active as we’re not unsubscribing it.

Maybe you are wondering what the problem with. We can just unsubscribe it on Destroy. Very simple…! But in real-time projects, each component may have 4 to 5 subscriptions and we have to manually unsubscribe all these in our ngOnDestroy. What if you forgot to unsubscribe from any one of the subscriptions…? It is going to cause memory leaks and performance issues.

Now let’s talk about the solution,

There are several ways to solve this problem. These are a few things we’ve to keep in mind while ideating for the solutions:

  • The solution has to be clean and easy to maintain.
  • Must reduce code repetition.
  • Must abide by SOLID principles.

Let’s explore one such approach using angular’s latest features.


    The Solution

    The idea is to create a helper class that manages all the subscriptions and it’s lifecycle.

    I created a class named “SubscriptionHandler” with the below code:

    import { Subscription } from 'rxjs';
    
    class SubscriptionHelper {
      private _subscriptions: Subscription[] = [];
    
      add(subscription: Subscription): void {
        this._subscriptions.push(subscription);
      }
    
      unsubscribe(): void {
        this._subscriptions.forEach((subscription: Subscription) => {
          subscription.unsubscribe();
        });
      }
    }
    
    export { SubscriptionHelper };

    Now in your component, just create an instance of this class and add your subscriptions to that instance.

    export class AppComponent {
      subscriptions: SubscriptionHelper;
      constructor() {
        this.subscriptions = new SubscriptionHelper();
      }
    
      listenFilterChange(): void {
        this.subscriptions.add(
          this.filterService.filterChange.subscribe((res) => {
            // Filter Logic goes here
          })
        );
      }
    }

    To unsubscribe from these subscriptions, just call the unsubscribe method from the OnDestroy.

    export class AppComponent implements OnDestroy {
      subscriptions: SubscriptionHelper;
      constructor() {
        this.subscriptions = new SubscriptionHelper();
      }
    
      listenFilterChange(): void {
        this.subscriptions.add(
          this.filterService.filterChange.subscribe((res) => {
            // Filter Logic goes here
          })
        );
      }
    
      ngOnDestroy(): void {
        this.subscriptions.unsubscribe();
      }
    }

    Looks good right..! But why do we have to unsubscribe manually? What if we forgot to do it somewhere? I didn’t find any solution for this in earlier versions of angular. But in Angular16, we have the DestroyRef provider. With the help of DestroyRef, I made a few changes to our SubscriptionHandler to avoid this one extra step.

    // subscribers helper class to unsubscribe from all subscriptions on component destroy
    
    import { DestroyRef } from '@angular/core';
    import { Subscription } from 'rxjs';
    
    class SubscriptionHelper {
      private _subscriptions: Subscription[] = [];
    
      constructor(destroyRef: DestroyRef) {
        destroyRef.onDestroy(() => {
          this.unsubscribe();
        });
      }
    
      add(subscription: Subscription): void {
        this._subscriptions.push(subscription);
      }
    
      unsubscribe(): void {
        this._subscriptions.forEach((subscription: Subscription) => {
          subscription.unsubscribe();
        });
      }
    }
    
    export { SubscriptionHelper };
    export class AppComponent{
      subscriptions: SubscriptionHelper;
      constructor(destroyRef: DestroyRef) {
        this.subscriptions = new SubscriptionHelper(destroyRef);
      }
    
      listenFilterChange(): void {
        this.subscriptions.add(
          this.filterService.filterChange.subscribe((res) => {
            // Filter Logic goes here
          })
        );
      }
    }

    With this small change, there is no need to manually call the unsubscribe method as well.

    What about earlier versions?

    Well, I’m exploring a few approaches for that as well like using a Component decorator with a helper class. But no luck so far. For now, we can go with the first approach of calling unsubscribe from the OnDestroy method.


    For some experts, this article may not help. But for those who are just starting out in angular, this article will help a lot. Let me know in the comments if you have any better approach or some suggestions.

    Share this article
    Leave a Reply

    Your email address will not be published. Required fields are marked *