Do I Need to Unsubscribe from an Observable in Angular?
One of the most common questions among Angular developers is: “Do I need to unsubscribe from my Observable?” The short answer is “It depends.” Not all Observables require manual unsubscription, but failing to unsubscribe from the wrong ones can cause memory leaks and performance issues.
With the release of Angular 16+, Angular now provides a built-in way to handle unsubscription automatically via takeUntilDestroyed()
, making it easier to manage Observables.
This blog will explore when you must unsubscribe, when you don’t need to, and the best modern practices for handling subscriptions in Angular 19.
✅ When You MUST Unsubscribe
If an Observable remains active even after the component is destroyed, you need to unsubscribe to prevent memory leaks. Here are the common cases where manual unsubscription is necessary.
1. Observables That Never Complete
Some Observables emit values indefinitely and never complete on their own. These include:
fromEvent()
(e.g., DOM event listeners)interval()
andtimer()
WebSocket
Observables- Custom
new Observable()
streams
📌 Example: fromEvent
must be unsubscribed to avoid accumulating event listeners.
import { Component, OnInit, OnDestroy } from "@angular/core";
import { fromEvent, Subscription } from "rxjs";
@Component({ selector: "app-example", template: "" })
export class ExampleComponent implements OnInit, OnDestroy {
private resizeSubscription!: Subscription;
ngOnInit() {
this.resizeSubscription = fromEvent(window, "resize").subscribe(() => {
console.log("Window resized");
});
}
ngOnDestroy() {
this.resizeSubscription.unsubscribe(); // Prevent memory leaks
}
}
2. Subscriptions to Subjects (Subject
, BehaviorSubject
, etc.)
Unlike regular Observables, Subjects do not automatically complete when a component is destroyed.
📌 Example: Unsubscribing from a Subject
prevents it from keeping references to dead components.
import { Subject } from "rxjs";
const subject = new Subject<number>();
const subscription = subject.subscribe(console.log);
subscription.unsubscribe(); // Must unsubscribe manually
3. Global Streams (Shared Services)
When using a service to share Observables across multiple components, you must manually unsubscribe when the component is destroyed, or it will remain active.
📌 Example: Unsubscribing from a shared data stream in a service.
@Injectable({ providedIn: "root" })
export class DataService {
private dataSubject = new Subject<string>();
data$ = this.dataSubject.asObservable();
sendData(value: string) {
this.dataSubject.next(value);
}
}
@Component({ selector: "app-example", template: "" })
export class ExampleComponent implements OnInit, OnDestroy {
private subscription!: Subscription;
constructor(private dataService: DataService) {}
ngOnInit() {
this.subscription = this.dataService.data$.subscribe(console.log);
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
}
🚀 When You DON’T Need to Unsubscribe
1. Finite Observables That Complete Automatically
Some Observables complete after emitting a finite number of values, meaning Angular automatically cleans them up.
✅ No need to unsubscribe for these Observables:
HttpClient.get()
(automatically completes after fetching data)of()
,range()
, andfrom()
(emit a fixed number of values and complete)take(1)
,first()
,last()
(force completion after one value)
📌 Example: HttpClient.get()
does not require unsubscription.
this.http.get("/api/data").subscribe(console.log); // No need to unsubscribe
📌 Example: take(1)
automatically unsubscribes.
interval(1000).pipe(take(1)).subscribe(console.log); // Runs only once, then unsubscribes
2. Using AsyncPipe
in Templates
If you subscribe to an Observable in an Angular template using AsyncPipe
, Angular handles unsubscription automatically when the component is destroyed.
📌 Example:
<div *ngIf="user$ | async as user"></div>
✅ No need to unsubscribe when using AsyncPipe
!
🔥 Best Practices for Unsubscribing in Angular 19
✅ New Way: takeUntilDestroyed()
(Angular 16+)
With takeUntilDestroyed()
, Angular automatically unsubscribes when the component is destroyed.
📌 Example: Using takeUntilDestroyed()
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
interval(1000)
.pipe(takeUntilDestroyed()) // Automatically unsubscribes on destroy
.subscribe(console.log);
✅ Using DestroyRef.onDestroy()
(Alternative Approach)
If you need manual cleanup, Angular 16+ provides DestroyRef.onDestroy()
.
📌 Example:
import { DestroyRef, inject } from "@angular/core";
const destroyRef = inject(DestroyRef);
destroyRef.onDestroy(() => console.log("Component destroyed!"));
❌ Avoid the Old Subject<void>
Approach
Before Angular 16, we used a Subject<void>
for cleanup. This is now unnecessary!
private destroy$ = new Subject<void>();
ngOnInit() {
someObservable.pipe(takeUntil(this.destroy$)).subscribe(console.log);
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
✅ Use takeUntilDestroyed()
instead.
🎯 Conclusion
- You must unsubscribe when dealing with long-lived Observables like
fromEvent
,interval
, andSubjects
. - You don’t need to unsubscribe from self-completing Observables like
HttpClient.get()
andAsyncPipe
. - Angular 16+ makes unsubscription easier with
takeUntilDestroyed()
andDestroyRef.onDestroy()
. - Avoid manual
Subject<void>
cleanup in modern Angular apps.
By following these best practices, you can keep your Angular applications efficient and free from memory leaks! 🚀
Do you use takeUntilDestroyed()
in your projects? Let me know your thoughts in the comments! 👇