Angular Subscribing And Unsubscribing Complete Guide
Understanding the Core Concepts of Angular Subscribing and Unsubscribing
Angular Subscribing and Unsubscribing
Understanding Observables and Subscriptions
Observables are used extensively in Angular for a variety of tasks including API responses, event handling, timers, and more. They provide a mechanism for pushing changes to a set of subscribers over time and can be thought of as collections that emit multiple async values.
A subscription is an object that represents the execution of an observable. Subscriptions allow you to start and stop the flow of data from an observable. When subscribing to an observable, you specify how the emitted value should be handled by providing a callback function, often using the subscribe()
method.
import { Observable, of } from 'rxjs';
const myObservable = of(1, 2, 3, 4, 5).pipe(
// operators can be added here
);
myObservable.subscribe({
next: (x) => console.log(x),
error: (e) => console.error(e),
complete: () => console.log('done'),
});
In this example, every emission from myObservable
triggers the next
function, logging the value. If there's an error or the observable completes, respective callbacks are triggered.
Common Scenarios Requiring Subscription Management
- HTTP Requests: When fetching data from an API, it's important to unsubscribe when the component is destroyed to ensure no hanging requests.
- Component Communication: Using observables for communication between components.
- Routing Guards: In scenarios where guarding routes involves subscriptions.
- Shared Services: Subscribing to services shared across multiple components, which may lead to unintended data flow if not managed properly.
Why Memory Leaks Happen?
Memory leaks occur when an application retains references to objects that are no longer needed. For observables in Angular, memory leaks can happen if you subscribe to an observable (such as an HTTP request) but forget to unsubscribe when the component that created the subscription is destroyed. This keeps the component in memory even after it's no longer visible, continuing to receive data updates and causing unnecessary resource consumption.
To demonstrate the importance of unsubscribing:
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs';
import { DataService } from './data.service';
@Component({
selector: 'app-my-component',
template: `<div>{{ data }}</div>`,
})
export class MyComponent implements OnInit, OnDestroy {
data: string;
subscription: Subscription;
constructor(private dataService: DataService) {}
ngOnInit(): void {
this.subscription = this.dataService.getData().subscribe(
(d) => (this.data = d)
);
}
ngOnDestroy(): void {
this.subscription.unsubscribe();
}
}
Here, MyComponent
subscribes to an observable provided by DataService
. It stores the subscription in a variable and calls .unsubscribe()
within the lifecycle hook ngOnDestroy
.
Strategies for Automatic Unsubscription
Using manual subscription management as shown above can become cumbersome, especially in larger applications. Hence, several strategies exist for automatically handling unsubscriptions:
Subscription Add Method: You can use one
Subscription
to hold multiple subscriptions and then callunsubscribe()
on that single subscription.import { Component, OnInit, OnDestroy } from '@angular/core'; import { Subscription } from 'rxjs'; import { DataService } from './data.service'; @Component({ selector: 'app-my-component', template: `<div>{{ data }}</div>`, }) export class MyComponent implements OnInit, OnDestroy { data: string; private allSubscriptions = new Subscription(); constructor(private dataService: DataService) {} ngOnInit(): void { this.allSubscriptions.add( this.dataService.getData().subscribe((d) => (this.data = d)) ); this.allSubscriptions.add( this.dataService.getMoreData().subscribe((g) => { // handle more data }) ); } ngOnDestroy(): void { this.allSubscriptions.unsubscribe(); } }
TakeUntil Operator: The
takeUntil
operator helps you complete your observable when another observable emits a value. Typically, you would use NgRx Store actions, or a Subject that emits when the component is destroyed.import { Component, OnInit, OnDestroy } from '@angular/core'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; import { DataService } from './data.service'; @Component({ selector: 'app-my-component', template: `<div>{{ data }}</div>`, }) export class MyComponent implements OnInit, OnDestroy { data: string; private destroy$ = new Subject<void>(); constructor(private dataService: DataService) {} ngOnInit(): void { this.dataService.getData() .pipe(takeUntil(this.destroy$)) .subscribe((d) => (this.data = d)); } ngOnDestroy(): void { this.destroy$.next(); this.destroy$.complete(); } }
Async Pipe: The
async
pipe automatically unsubscribes when the current view changes. While not ideal for all use cases (especially where you need to perform multiple operations or complex transformations), it is very convenient for simple scenarios.import { Component } from '@angular/core'; import { DataService } from './data.service'; @Component({ selector: 'app-my-component', template: `<div>{{ data$ | async }}</div>`, // automatically unsubscribes }) export class MyComponent { data$ = this.dataService.getData(); constructor(private dataService: DataService) {} }
Conclusion
Managing subscriptions effectively is vital for optimal performance and avoiding memory leaks in Angular applications. By understanding the importance of subscription cleanup and utilizing best practices such as takeUntil
, manual management with Subscription
, or leveraging the async
pipe, developers can maintain clean and efficient reactive code.
Online Code run
Step-by-Step Guide: How to Implement Angular Subscribing and Unsubscribing
Step-by-Step Guide: Subscribing and Unsubscribing in Angular
Step 1: Setting Up the Environment
First, make sure you have Angular CLI installed globally. If not, you can install it using npm:
npm install -g @angular/cli
Create a new Angular project:
ng new observable-app
cd observable-app
Step 2: Creating a Service
Generate a new service that will emit some data using an observable.
ng generate service data
Edit src/app/data.service.ts
to return an observable:
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class DataService {
// Creating an Observable that emits a number every second
getData(): Observable<number> {
return new Observable((observer) => {
let count = 0;
const intervalId = setInterval(() => {
observer.next(count++);
}, 1000);
return {
unsubscribe() {
clearInterval(intervalId);
}
};
});
}
}
Step 3: Creating a Component
Generate a component that will subscribe to the observable and display the data:
ng generate component counter
Edit src/app/counter/counter.component.ts
:
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs';
import { DataService } from '../data.service';
@Component({
selector: 'app-counter',
template: `
<h2>Counter: {{ counterValue }}</h2>
`,
styles: [``]
})
export class CounterComponent implements OnInit, OnDestroy {
counterValue: number;
subscription: Subscription;
constructor(private dataService: DataService) {}
ngOnInit(): void {
// Subscribing to the observable from DataService
this.subscription = this.dataService.getData().subscribe((value) => {
this.counterValue = value;
});
}
ngOnDestroy(): void {
// Unsubscribing from the observable to prevent memory leaks
this.subscription.unsubscribe();
}
}
Step 4: Updating the App Module
Ensure that the CounterComponent
is declared in the AppModule
:
Edit src/app/app.module.ts
:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { CounterComponent } from './counter/counter.component';
@NgModule({
declarations: [
AppComponent,
CounterComponent
],
imports: [
BrowserModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Step 5: Using the Counter Component
Edit src/app/app.component.html
to include the CounterComponent
:
<app-counter></app-counter>
Step 6: Running the Application
Run the application using Angular CLI:
ng serve
Navigate to http://localhost:4200
in your web browser. You should see the counter value updating every second.
Step 7: Understanding Subscription Management
- Subscription: An object with an
unsubscribe()
method that cleans up the resource or data source. - ngOnInit: This lifecycle hook is called when the component is initialized. Here, we subscribe to the
DataService
observable. - ngOnDestroy: This lifecycle hook is called when the component is destroyed. We use it to unsubscribe from the observable to prevent memory leaks.
Conclusion
In Angular, managing subscriptions properly is crucial to avoid memory leaks. By following these steps, you can successfully subscribe to and unsubscribe from observables. This pattern ensures that your application remains performant and efficient.
If you want to handle multiple subscriptions in a component, you can use Subscription
chaining:
Top 10 Interview Questions & Answers on Angular Subscribing and Unsubscribing
Top 10 Questions and Answers on Angular Subscribing and Unsubscribing
1. What is Subscribing in Angular?
Answer: In Angular, subscribing involves listening to an Observable
to receive asynchronous data or notifications. An Observable
is a core concept of Reactive Extensions (RxJS), which Angular uses for handling asynchronous operations such as HTTP requests, events, or streams of data. When you subscribe to an Observable
, you provide a callback function that will be executed when the Observable
emits a value.
2. Why do we need to Unsubscribe in Angular?
Answer: Unsubscribing is crucial because it prevents memory leaks by removing listeners that are no longer needed. When an Observable
is subscribed to, Angular creates a subscription that will continue to receive updates until it is unsubscribed. If a component subscribes to an Observable
but doesn’t unsubscribe when the component is destroyed or the subscription is no longer needed, the subscription will remain active, causing memory leaks and potentially leading to performance issues.
3. How do you Unsubscribe in Angular?
Answer: You can unsubscribe in Angular by calling the unsubscribe()
method on a Subscription
object. Typically, you store the subscription in a variable and call unsubscribe()
in the ngOnDestroy
lifecycle hook of the component. For example:
export class MyComponent implements OnDestroy {
private subscription: Subscription;
constructor(private myService: MyService) {
this.subscription = this.myService.getObservable().subscribe(data => {
// handle data
});
}
ngOnDestroy() {
if (this.subscription) {
this.subscription.unsubscribe();
}
}
}
4. What is the advantage of using a Subscription Store?
Answer: Using a Subscription
store (or an array to hold all subscriptions) can be useful when a component has multiple subscriptions. By storing all subscriptions in an array, you can easily unsubscribe from all of them in the ngOnDestroy
method with a single line of code:
export class MyComponent implements OnDestroy {
private subscriptions: Subscription[] = [];
constructor(private myService: MyService) {
this.subscriptions.push(this.myService.getObservable().subscribe(data => {
// handle data
}));
}
ngOnDestroy() {
this.subscriptions.forEach(sub => sub.unsubscribe());
}
}
5. Can you use takeUntil
for Automatic Unsubscription?
Answer: Yes, takeUntil
is an operator provided by RxJS that allows you to automatically unsubscribe from an Observable
when another Observable
emits a value, typically a component's ngOnDestroy event. This can make your code cleaner:
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
export class MyComponent implements OnDestroy {
private destroy$ = new Subject<void>();
constructor(private myService: MyService) {
this.myService.getObservable().pipe(
takeUntil(this.destroy$)
).subscribe(data => {
// handle data
});
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
}
6. Why should you Unsubscribe from HttpRequests?
Answer: While Angular's HttpClient
automatically unsubscribes from HTTP requests when the component is destroyed, it's still a good practice to manage subscriptions explicitly, especially in larger applications where components might be dynamically created and destroyed. Unsubscribing ensures that any ongoing HTTP requests are terminated when they are no longer needed, reducing the risk of memory leaks.
7. How do you Unsubscribe from RxJS Subjects?
Answer: RxJS Subjects are both Observable
and Observer
, and they can be subscribed to just like any other Observable
. To unsubscribe from a Subject, you also use the unsubscribe()
method on the Subscription
object:
export class MyComponent implements OnDestroy {
private subscription: Subscription;
constructor(private mySubject: Subject<any>) {
this.subscription = this.mySubject.subscribe(data => {
// handle data
});
}
ngOnDestroy() {
if (this.subscription) {
this.subscription.unsubscribe();
}
}
}
8. What happens if you forget to Unsubscribe?
Answer: Forgetting to unsubscribe can lead to memory leaks, especially in components that create multiple subscriptions or that persist across multiple navigation states. Memory leaks occur because the Observable
continues to emit values and maintain a reference to the callback function, preventing the garbage collector from releasing the memory used by the component and its subscriptions.
9. Is there any way to automatically Unsubscribe in Angular without manually calling unsubscribe()
?
Answer: Yes, there are tools and libraries that can help manage subscriptions automatically. One such tool is ngneat/until-destroy, which is a decorator that simplifies unsubscribing by automatically calling unsubscribe()
on all subscriptions when the component is destroyed:
import { untilDestroyed } from '@ngneat/until-destroy';
@Component({
selector: 'app-my-component',
templateUrl: './my-component.component.html',
})
@untilDestroyed() // DECORATOR
export class MyComponent implements OnDestroy {
constructor(private myService: MyService) {
this.myService.getObservable().pipe(
untilDestroyed(this) // HELPER FUNCTION
).subscribe(data => {
// handle data
});
}
ngOnDestroy() {}
}
10. When should you NOT unsubscribe?
Answer: In some situations, you might not need to unsubscribe:
- Hot Observables: These are shared
Observables
where data is emitted regardless of whether there are subscribers or not (e.g.,Subject
,BehaviorSubject
,ReplaySubject
). If aSubject
continues emitting values after the component is destroyed, it may be intentional. - Static or Long-Lived Observables: If the
Observable
is static, long-lived, and does not retain references to components, you might not need to unsubscribe. However, it’s generally safer to manage subscriptions explicitly.
Login to post a comment.