Unsubscribing From Observable In Angular
Let's learn how to Unsubscribe from an observable in Angular in this article. Memory leak and performance deterioration will occur if an observable is not unsubscribed.
Why Unsubscribe
In the example below, we have ChildComponent, which in its ngOnInit hook subscribes to an observable. Every 2000 milliseconds, the observable emits a new value.
The ngIf is used in the AppComponent to show and hide the ChildComponent.
Add the following code under the app.component.html:
<h1>Angular Unsubscribe from Observable Example</h1> Show Child : <input type="checkbox" id="showChild" name="showChild" import { Component, VERSION } from "@angular/core"; @Component({ selector: "my-app", template: ` <h1>Angular Unsubscribe from Observable Example</h1> Show Child : <input type="checkbox" id="showChild" name="showChild" [(ngModel)]="showChild" /> <app-child-component *ngIf="showChild"></app-child-component> `, styleUrls: ["./app.component.css"] }) export class AppComponent { name = "Angular " + VERSION.major; showChild = false; }
Add the following code under the Child.Component.ts:
import { Component, OnInit } from "@angular/core"; import { timer, interval } from "rxjs"; @Component({ selector: "app-child-component", templateUrl: "./child.component.html", styleUrls: ["./child.component.css"] }) export class ChildComponent implements OnInit { src = interval(2000); id = Date.now(); constructor() {} ngOnInit() { console.log("Component Created " + this.id); this.src.subscribe(value => { console.log("Received " + this.id); }); } ngOnDestroy() { console.log("Component Destroyed " + this.id); } }
When Angular renders the child component, the subscription begins to emit values. When we remove the component, however, the observable continues to emit new values. This is visible in the console window.
When we render the child component again, a new subscription is started. We currently have two subscriptions active. When a new instance of the child component is created, a new subscription is created as well, and those subscriptions are never cleaned up.
How to Unsubscribe
Unsubscribing from an observable is as simple as using the subscription's Unsubscribe() method. It cleans up all listeners and frees up memory space.
To do so, first, make a variable to hold the subscription information.
obs: Subscription;
Assign the obs variable to the subscription.
this.obs = this.src.subscribe(value => { console.log("Received " + this.id); });
In the ngOnDestroy method, call the unsubscribe() method.
ngOnDestroy() { this.obs.unsubscribe(); }
The observable gets unsubscribed and cleaned up when we destroy the component.
The completed code can be found below.
Add the following code under the Child.Component.ts:
import { Component, OnInit } from "@angular/core"; import { timer, interval, Subscription } from "rxjs"; @Component({ selector: "app-child-component", templateUrl: "./child.component.html", styleUrls: ["./child.component.css"] }) export class ChildComponent implements OnInit { src = interval(2000); id = Date.now(); obs: Subscription; constructor() {} ngOnInit() { console.log("Component Created " + this.id); this.obs = this.src.subscribe(value => { console.log("Received " + this.id); }); } ngOnDestroy() { console.log("Component Destroyed " + this.id); this.obs.unsubscribe(); } }
When to Unsubscribe
It is not necessary to unsubscribe from each and every subscription. Consider the observables, which are finite by definition. It will not harm you if you do so.
In the following cases, you do not need to unsubscribe in Angular.
- HTTP get and post, for example, are HttpClient Observables. They are finished when only one value is returned.
- Angular Router additionally emits observables such as paramMap, queryParamMap, fragment, data, URL, Events, and so on. Unsubscribing is handled by the Router Modules.
- All observables are finite.
In the following cases, you must unsubscribe.
- Any Observable you construct in an Angular component or in an Angular service.
- Angular Forms observables ValueChanges and StatusChanges
- The Renderer2 service's DOM events are monitored by this.
- There are no limits to the number of variables that can be observed.
- Always Unsubscribe if you're unsure.
Various ways to Unsubscribe
Use Async Pipe
When we use the Async pipe to subscribe to an observable, it cleans up automatically when the component is destroyed.
Using Take or First Operator
Using the Take or First Operators, convert all infinite observables to finite observables.
The Take Operator emits the first n values before terminating the source observable.
export class AppComponent { obs = of(1, 2, 3, 4, 5).pipe(take(2)); ngOnInit() { this.obs.subscribe(val => console.log(val)); } } ****Console ****** 1 2
The first operation starts the observable and then emits the first value. However, if it does not get any value, it sends an error notification.
export class AppComponent { obs = of(1, 2, 3, 4, 5).pipe(first()); ngOnInit() { this.obs.subscribe(val => console.log(val)); } } ****Console ****** 1
Use Unsubscribe
The simplest and most straightforward method is to use Unsubscribe.
In the ngOnDestroy hook, store each subscription in a local variable and call unsubscribe on it.
import { Component, OnInit } from "@angular/core"; import { timer, interval, Subscription } from "rxjs"; @Component({ selector: "app-child-component", templateUrl: "./child.component.html", styleUrls: ["./child.component.css"] }) export class ChildComponent implements OnInit { src1 = interval(1000); src2 = interval(1500); src3 = interval(2000); id = Date.now(); obs1: Subscription; obs2: Subscription; obs3: Subscription; constructor() {} ngOnInit() { console.log("Component Created " + this.id); this.obs1 = this.src1.subscribe(value => { console.log("Src1 " + this.id); }); this.obs2 = this.src2.subscribe(value => { console.log("Src2 " + this.id); }); this.obs3 = this.src3.subscribe(value => { console.log("Src3 " + this.id); }); } ngOnDestroy() { if (this.obs1) this.obs1.unsubscribe(); if (this.obs2) this.obs2.unsubscribe(); if (this.obs3) this.obs3.unsubscribe(); console.log("Component Destroyed " + this.id); } }
Using Array to store subscription
You can build an array and add each subscription to it instead of using a local variable for each subscription.
let subs: Subscription[] = [];
Each subscription is added to an array.
this.subs.push( this.src1.subscribe(value => { console.log("Src1 " + this.id); }) );
Call unsubscribe on each subscription in the ngOnDestroy hook.
ngOnDestroy() { this.subs.forEach(sub => sub.unsubscribe()); console.log("Component Destroyed " + this.id); }
Using TakeUntil
The TakeUntil Operator can be used.
Until the notifier Observable emits a value, the takeUntil operator emits values from the source observable. The source observable is then completed.
To use takeUntil, we must first establish an observable stop$ notifier.
stop$ = new Subject<void>();
When a component is destroyed, this notifier observable emits a value. This is done in the ngOnDestroy hook.
ngOnDestroy() { this.stop$.next(); this.stop$.complete(); }
TakeUntil(this.stop$) is added to all the observables we subscribe to. All of them are immediately unsubscribed when the component is destroyed. Remember to put it last in the operator's pipe.
this.src.pipe(takeUntil(this.stop$)).subscribe(value => { console.log("Obs1 " + this.id); });
The following code is the complete code of the child.component.ts file:
import { Component, OnInit } from "@angular/core"; import { timer, interval, Subscription, Subject } from "rxjs"; import { takeUntil } from "rxjs/operators"; @Component({ selector: "app-child-component", templateUrl: "./child.component.html", styleUrls: ["./child.component.css"] }) export class ChildComponent implements OnInit { stop$ = new Subject<void>(); src = interval(2000); id = Date.now(); constructor() {} ngOnInit() { console.log("Component Created " + this.id); this.src.pipe(takeUntil(this.stop$)).subscribe(value => { console.log("Obs1 " + this.id); }); this.src.pipe(takeUntil(this.stop$)).subscribe(value => { console.log("Obs2 " + this.id); }); } ngOnDestroy() { this.stop$.next(); this.stop$.complete(); console.log("Component Destroyed " + this.id); } }
Using TakeUntil in a base component
You can create a notifier observable in a BaseComponent and reuse it instead of generating one in each component.
Add the following code under the base.component.ts file:
import { Component, OnDestroy } from "@angular/core"; import { Subject } from "rxjs"; @Component({ template: `` }) export class BaseComponent implements OnDestroy { stop$ = new Subject<void>(); ngOnDestroy() { this.stop$.next(); this.stop$.complete(); console.log("BaseComponent Destroyed "); } }
Use BaseComponent to extend each component
Add the following code under the child.component.ts file:
import { Component, OnInit } from "@angular/core"; import { timer, interval, Subscription, Subject } from "rxjs"; import { takeUntil } from "rxjs/operators"; import { BaseComponent } from "../base.component"; @Component({ selector: "app-child-component", templateUrl: "./child.component.html", styleUrls: ["./child.component.css"] }) export class ChildComponent extends BaseComponent implements OnInit { src = interval(2000); id = Date.now(); constructor() { super(); } ngOnInit() { console.log("Component Created " + this.id); this.src.pipe(takeUntil(this.stop$)).subscribe(value => { console.log("Obs1 " + this.id); }); this.src.pipe(takeUntil(this.stop$)).subscribe(value => { console.log("Obs2 " + this.id); }); } ngOnDestroy() { super.ngOnDestroy(); console.log("Component Destroyed " + this.id); } }
Conclusion
In this article, we learned how to use Unsubscribing from observable in angular with an example application.
I hope this article helps you and you will like it.👍
If you have any doubt or confusion then free to ask in the comment section.