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() and timer()
  • 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(), and from() (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, and Subjects.
  • You don’t need to unsubscribe from self-completing Observables like HttpClient.get() and AsyncPipe.
  • Angular 16+ makes unsubscription easier with takeUntilDestroyed() and DestroyRef.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! 👇