Pages

Unsubscribing an observable in Angular 6

Many people have questions regarding observable subscriptions (or executions) :- What is meant by unsubscribing an observable execution? Do we need to unsubscribe? When and how should we unsubscribe? etc. Its always the what, why, when and how that we need to know and that describes everything. So I will follow the obvious sequence to answer these questions.

What is meant by unsubscribing an observable execution?

When we call "subscribe()" on an observable we actually start an observable execution which then deliver values or events to the Observer of that execution. In addition to creating an observable execution the "subscribe()" method also returns a "Subscription" object which is a handle to the ongoing execution. This "Subscription" object has an "unsubscribe()" method by which you can cancel the ongoing observable execution. So unsubscribing an observable execution means cancelling that execution and dispose resources of that execution.

Do we need to unsubscribe (dispose) an observable subscription (execution)?

The answer is yes we do need to dispose an observable execution in order to avoid memory leaks and wastage of computation power.

But, do we need to unsubscribe all the observables? The answer is No, you don't. You only need to unsubscribe infinite observables (or the observables that does not completes) for example the "interval()" observable or the DOM event listener observable, this implies that you don't usually need to manually unsubscribe from finite observables (or the observables that completes for example httpClient observables etc.) unless you want to prematurely end the observable execution (in case you find out your work is done with an observable and you don't need it any more then you can decide to dispose finite observable execution as well) or the observable is gonna have a greater lifespan that the component, service or directive in which it is subscribed. But there are exceptions to this rule as well, you don't need to unsubscribe infinite observables subscribed by angular itself for example when you are using async pipe, in this case the Angular framework will unsubscribe the observable automatically when the component is destroyed.

For even more clarification on which observables need to be unsubscribed and which are not follow the StackOverflow link mentioned at the end of this post. The important summary you will extract from this link is that "In most cases we will not need to explicitly call the unsubscribe method unless we want to cancel early or our Observable has a longer lifespan than our subscription."

When should we unsubscribe (dispose) it?

So the next question is, when should we unsubscribe a subscription? The obvious answer is when we no longer need it. But it would be cumbersome to identify when we are done with an observable execution for each observable that we use and unsubscribe it as soon as we are done with it, also there is risk of unsubscribing too early. A much more safer, simple, reliable and also the recommended method is to unsubscribe when the directive, pipe or service is destroyed and Angular has an "ngOnDestroy" lifecycle hook  that is called at that time. So all you have to do is simply unsubscribe in the "ngOnDestroy" lifecycle hook of your directive, pipe or service.

Is there any side effect of unsubscribing an already completed observable execution?

No, not really, but it will be more or less useless and an overhead to call unsubscribe on an already completed observable execution.

How should we unsubscribe?

Well, there are two ways to do that, we will explore them one by one :-
  1. Calling unsubscribe on the subscription object
    This is the easiest way of disposing an observable execution. All you need to do is to call the "unsubscribe()" method on the subscription that you get when you subscribe to an observable. See the below example for refrence :
    import { Component, OnInit, OnDestroy } from '@angular/core';
    import { interval } from 'rxjs';
    
    
    @Component({
        selector: 'app-root',
        templateUrl: './app.component.html'
    })
    export class AppComponent implements OnInit, OnDestroy {
        private subscription;
        constructor() { }
    
        ngOnInit() {
            var observable = interval(1000);
            this.subscription = observable.subscribe(x => console.log(x));
        }
    
        ngOnDestroy() {
            this.subscription.unsubscribe();
        }
    
    }
    

    In case you have more than one subscriptions then you can combine them into one, so that multiple subscriptions can be unsubscribed by just calling unsubscribe() of one subscription, instead of creating multiple variables for holding subscriptions and unsubscribing them individually.
    Please follow the below example for refrence :
    import { Component, OnInit, OnDestroy } from '@angular/core';
    import { interval } from 'rxjs';
    
    @Component({
        selector: 'app-root',
        templateUrl: './app.component.html'
    })
    export class AppComponent implements OnInit, OnDestroy {
        private subscription;
        constructor() { }
    
        ngOnInit() {
            var observable1 = interval(1000);
            var observable2 = interval(2000);
    
            this.subscription = observable1.subscribe(x => console.log('observable1: ' + x));
            var childSubscription = observable2.subscribe(x => console.log('observable2: ' + x));
    
            this.subscription.add(childSubscription);
        }
    
        ngOnDestroy() {
            this.subscription.unsubscribe();
        }
    }
    
    

  2. Using RxJS operators
    Although the first option is pretty straight forward but there is an even better way of unsubscribing observable subscriptions, that is, by using the RxJS operators such as the takeUntil operator. This is also the recommended approach for unsubscribing. Below is an example to demonstrate how to use it :
    import { Component, OnInit, OnDestroy } from '@angular/core';
    import { interval, Subject } from 'rxjs';
    import { takeUntil } from 'rxjs/operators';
    
    @Component({
        selector: 'app-root',
        templateUrl: './app.component.html'
    })
    export class AppComponent implements OnInit, OnDestroy {
        private ngUnsubscribe = new Subject();
    
        constructor() { }
    
        ngOnInit() {
            var observable1 = interval(1000);
            var observable2 = interval(2000);
    
            observable1.pipe(takeUntil(this.ngUnsubscribe)).subscribe(x => console.log('observable1: ' + x));
            observable2.pipe(takeUntil(this.ngUnsubscribe)).subscribe(x => console.log('observable2: ' + x));
        }
    
        ngOnDestroy() {
            this.ngUnsubscribe.next();
            this.ngUnsubscribe.complete();
        }
    }
    
    

    There are a few other RxJS operators that can be used as an alternative to takeUntil in unsubscribing or completing an observable, for example takeWhile(), first(), take() and elementAt(). To get further information on using these operators as an alternative of  unsubscribing follow the link. Apart from all these remember you can stay away from all this subscription management in angular by using the AsyncPipe where ever possible. 

Further reading :-

  • Here is an useful SO link to get further clarification on How to unsubscribe and which subscriptions need to be unsubscribed and which not. 

No comments:

Post a Comment