Subscriptions & Unsubscription
Managing subscriptions is critical in Angular. Unsubscribe to prevent memory leaks, zombie callbacks, and mysterious bugs.
A Subscription is what you get back when you call .subscribe() on an Observable. It represents the execution and lets you cancel it.
import { Subscription } from 'rxjs';
// .subscribe() returns a Subscription object
const subscription = interval(1000).subscribe(v => console.log(v));
// Later โ cancel the subscription
subscription.unsubscribe(); // Stops the interval
// Check if already unsubscribed
console.log(subscription.closed); // true
"Subscription = newspaper subscription โ unsubscribe karo toh paper nahi aayega."
Key Subscription methods:
unsubscribe()โ cancel the Observable executionclosedโ boolean, true if unsubscribedadd(childSub)โ add child subscriptions โ calling unsubscribe() on parent unsubscribes all children
The add() method is especially useful โ you can collect multiple subscriptions and unsubscribe all at once.
If you don't unsubscribe, the Observable keeps running even after the component is destroyed.
"Bina unsubscribe ke โ TV off karke bill bhi aa raha hai."
What happens when you forget to unsubscribe:
// BAD โ never unsubscribes!
export class BadComponent implements OnInit {
ngOnInit() {
interval(1000).subscribe(v => {
console.log('Still running!', v);
// This code runs even after component is destroyed!
});
}
// No ngOnDestroy to clean up!
}
Three symptoms of subscription leaks:
- Memory leak โ the callback function reference stays in memory
- Zombie callbacks โ code tries to update a destroyed component's properties
- "ExpressionChangedAfterItHasBeenCheckedError" โ weird Angular change detection errors
HTTP calls from HttpClient usually complete after one response, so they don't cause memory leaks. But it's still good practice to clean up.
The traditional way โ collecting subscriptions and unsubscribing in ngOnDestroy.
import { Component, OnInit, OnDestroy, inject } from '@angular/core';
import { Subscription, interval } from 'rxjs';
import { HttpClient } from '@angular/common/http';
export class OldStyleComponent implements OnInit, OnDestroy {
private http = inject(HttpClient);
// Collect all subscriptions in one Subscription object
private subscriptions = new Subscription();
ngOnInit() {
// Add each subscription
this.subscriptions.add(
interval(1000).subscribe(v => console.log(v))
);
this.subscriptions.add(
this.http.get('/api/biryani').subscribe(data => {
this.biryanis = data;
})
);
}
ngOnDestroy() {
// Unsubscribe ALL at once
this.subscriptions.unsubscribe();
}
}
"Manual = alag alag bills collect karo, ek saath pay karo."
Pros: Explicit, works in all Angular versions
Cons: Boilerplate, must remember to add to collection, forget once โ memory leak
Still valid but the new ways are cleaner.
Angular 16+ introduced takeUntilDestroyed() โ the cleanest way to auto-unsubscribe.
import { Component, inject, DestroyRef } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { interval } from 'rxjs';
export class NewStyleComponent {
private destroyRef = inject(DestroyRef);
constructor() {
interval(1000).pipe(
takeUntilDestroyed(this.destroyRef) // Auto-cleanup!
).subscribe(v => console.log(v));
}
// No ngOnDestroy, no Subscription variable needed!
}
"takeUntilDestroyed = auto-debit โ khud ho jayega, tum fikar mat karo."
How it works:
takeUntilDestroyed(destroyRef)is an RxJS operator- When the component/cleanup context is destroyed, it automatically unsubscribes
- No need for
ngOnDestroy, noSubscriptionvariable - Requires
DestroyRefto be injected
โ ๏ธ Important: Must inject DestroyRef and pass it to takeUntilDestroyed(). Passing destroyRef ensures it works in all injection contexts.
Several other patterns for automatic cleanup:
1. Async Pipe โ {{ data$ | async }}:
// Component
data$ = this.http.get('/api/biryani');
// Template โ auto subscribes AND unsubscribes on destroy
<div *ngFor="let biryani of data$ | async">
{{ biryani.name }}
</div>
2. toSignal() โ Convert Observable to Signal:
import { toSignal } from '@angular/core/rxjs-interop';
data = toSignal(
this.http.get('/api/biryani').pipe(catchError(() => of([]))),
{ initialValue: [] }
);
// No subscribe, no unsubscribe โ just a signal!
3. take(1) or first() โ Auto-complete after one value:
import { first } from 'rxjs/operators';
this.http.get('/api/biryani/42').pipe(
first() // Auto-completes after first (and only) value
).subscribe(data => console.log(data));
"Jitne tareeqe hain utne options โ kaam karo jo easy lage."
Recommendation: For new code, prefer toSignal() for data (components) and takeUntilDestroyed() for side effects (logging, navigation). Use async pipe in templates for observables.
Key Takeaways
- โ .subscribe() returns a Subscription โ .unsubscribe() cancels it, .closed checks state
- โ Forgetting to unsubscribe = memory leak + zombie callbacks + weird errors
- โ Old way: new Subscription() + .add(child) + ngOnDestroy unsubscribe
- โ New way: takeUntilDestroyed(this.destroyRef) โ no ngOnDestroy needed
- โ Other patterns: async pipe (template), toSignal() (reactive), take(1)/first() (auto-complete)
Want to track your progress?
Log in to save your place and pick up where you left off.
Progress track karna chahte ho?
Login karo apni progress save karne ke liye aur jahan chhoda tha wahan se shuru karo.
Login