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
- Open DevTools → Memory
- Record a heap snapshot
- Trigger component mount/unmount cycles
- 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/