How to unsubscribe in Angular (the 2026 guide)

Knowing how to unsubscribe in Angular is what keeps RxJS subscriptions from leaking memory. This guide covers every approach — the async pipe, takeUntilDestroyed, takeUntil, manual teardown — when each fits, the current best practice, and how to confirm you didn't miss one.

Do you always need to unsubscribe?

No — and this is the part most guides get wrong. You only need to unsubscribe from streams that don't complete on their own. An HttpClient call completes after one emission; a stream piped through take(1) or first() completes too. You do need to unsubscribe from long-lived streams: valueChanges, fromEvent, interval, route params, store selectors.

The ways to unsubscribe, ranked

1. The async pipe (preferred)

If the value is only used in the template, bind the Observable with | async and let Angular manage the subscription. Nothing to remember, nothing to leak.

@if (user$ | async; as user) {
  {{ user.name }}
}

2. takeUntilDestroyed() (Angular 16+)

For imperative subscriptions, pipe takeUntilDestroyed(). Called in an injection context it ties the stream to the component/service lifecycle automatically.

constructor() {
  this.form.valueChanges
    .pipe(takeUntilDestroyed())
    .subscribe(v => this.save(v));
}

Deep dive: takeUntil vs takeUntilDestroyed.

3. takeUntil(destroy$) (classic)

Before Angular 16, the idiom was a destroy Subject completed in ngOnDestroy. Still valid, more boilerplate.

private destroy$ = new Subject<void>();

ngOnInit() {
  this.svc.data$.pipe(takeUntil(this.destroy$)).subscribe(/* … */);
}
ngOnDestroy() {
  this.destroy$.next();
  this.destroy$.complete();
}

4. Manual unsubscribe / SubSink

Hold the Subscription and call unsubscribe() in ngOnDestroy, or collect several with a helper. See SubSink compared. Most error-prone — easiest to forget one.

5. @UntilDestroy() decorator

until-destroy removes the destroy-subject boilerplate via a decorator + untilDestroyed(this). Convenient; still opt-in per stream.

Best practice in 2026

  • Template-only value → async pipe.
  • Imperative subscription → takeUntilDestroyed().
  • Pre-16 codebase → takeUntil(destroy$) or until-destroy.
  • Whatever you pick, apply it consistently — mixed conventions are where leaks hide.

How to know you didn't miss one

Every approach above is opt-in: it only protects streams you remember to wire. The silent failure is the one subscribe() without teardown. Run rxjs-leak-finder in dev — one line in main.ts — and it flags any subscription that outlived its owner, with the stack trace pointing at the exact line.

// main.ts (dev only)
import { isDevMode } from '@angular/core';
import { enableRxjsLeakDetector } from 'rxjs-leak-finder';

if (isDevMode()) {
  enableRxjsLeakDetector();
}

Background on the bug class itself: RxJS memory leaks explained.

FAQ

What happens if I don't unsubscribe in Angular?

For long-lived streams, the subscription and everything it captured stay in memory after the component is destroyed — a leak. Memory grows over time and stale callbacks keep firing.

Is the async pipe enough on its own?

For template-bound streams, yes. You still need a teardown strategy for anything you subscribe to in TypeScript.