How To Fix Memory Leaks from Subscriptions in Angular?

Memory leaks are one of the most common performance issues in Angular applications. They occur mostly when Observable subscriptions remain active even after components are destroyed. These leaks slowly consume browser memory, degrade performance, and may cause the app to freeze or crash.

This guide explains why memory leaks happen, how to spot them, and the best ways to fix and prevent them in Angular.


1. What Causes Memory Leaks in Angular?

Angular apps commonly use Observables from:

  • HTTP requests
  • Event streams
  • Interval timers
  • Form valueChanges
  • Router events
  • Custom services

A memory leak occurs when a subscription:

  • Is not unsubscribed when the component is destroyed
  • Keeps listening indefinitely
  • Holds references to DOM or component data

This prevents garbage collection, gradually increasing memory usage.


2. Symptoms of Memory Leaks

You may notice:

  • UI getting slower over time
  • Tabs consuming excessive RAM
  • Page crashes on long usage
  • Re-render taking longer
  • Event handlers firing unexpectedly multiple times

3. How To Fix Memory Leaks in Angular

Here are the best methods to fix and prevent leaks.


Method 1: Manually Unsubscribe in ngOnDestroy

export class DemoComponent implements OnDestroy {
  sub: Subscription;

  ngOnInit() {
    this.sub = this.service.data$.subscribe(value => {
      console.log(value);
    });
  }

  ngOnDestroy() {
    this.sub.unsubscribe();
  }
}

✔ Best for simple, single subscriptions
✔ Easy to implement


Method 2: Use takeUntil (Best Practice)

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

ngOnInit() {
  this.service.data$
    .pipe(takeUntil(this.destroy$))
    .subscribe(data => console.log(data));
}

ngOnDestroy() {
  this.destroy$.next();
  this.destroy$.complete();
}

✔ Works for multiple subscriptions
✔ Clean, scalable pattern
✔ Most recommended in real apps


Method 3: Use Async Pipe (No Unsubscribe Needed)

<div *ngIf="data$ | async as data">
  {{ data }}
</div>

Async pipe automatically:

  • Subscribes
  • Unsubscribes when DOM element is removed

✔ Best for template-only Observables
✔ Zero memory leaks


Method 4: Use take(1) for One-Time Streams

this.http.get('/api/user')
  .pipe(take(1))
  .subscribe();

✔ Ideal for one-time HTTP calls
✔ Prevents persistent subscriptions


Method 5: Avoid Nested Subscriptions

Example of WRONG code:

this.service1.get().subscribe(val1 => {
  this.service2.get().subscribe(val2 => {
    // nested hell
  });
});

Fix using switchMap, mergeMap, etc.:

this.service1.get().pipe(
  switchMap(val1 => this.service2.get())
).subscribe(val2 => console.log(val2));

✔ Cleaner
✔ No chains of orphaned subscriptions


4. How To Detect Memory Leaks

Chrome DevTools Performance Tab

  1. Open DevTools → Memory
  2. Record a heap snapshot
  3. Trigger component mount/unmount cycles
  4. Compare snapshots

If memory continually grows → you have leaks.


5. Best Practices to Prevent Future Leaks

  • Prefer async pipes whenever possible
  • Use takeUntil pattern for component-level subscriptions
  • Avoid subscribing inside services unless necessary
  • Do not use nested subscriptions
  • Clean up eventListeners manually if used
  • Remove intervals and timers in ngOnDestroy

Citations

Internal: https://savanka.com/category/savanka-helps/
External: http://angular.dev/

Leave a Comment

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *