Skip to content

Memory Management 🧠

Effective memory management prevents memory leaks, reduces application bloat, and ensures smooth performance. Learn to identify and fix memory issues in Angular applications.

Common Causes:

  • Unsubscribed observables
  • Event listeners not removed
  • Timers not cleared
  • Detached DOM references
  • Circular references
  • Global variables

Symptoms:

  • Increasing memory usage over time
  • Slow performance
  • Browser crashes
  • Unresponsive UI
import { Component, OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs';
@Component({
selector: 'app-data',
standalone: true
})
export class DataComponent implements OnDestroy {
private subscription = new Subscription();
ngOnInit() {
// Add subscriptions
this.subscription.add(
this.dataService.getData().subscribe(data => {
console.log(data);
})
);
this.subscription.add(
this.userService.getUser().subscribe(user => {
console.log(user);
})
);
}
ngOnDestroy() {
// ✅ Clean up all subscriptions
this.subscription.unsubscribe();
}
}
import { Component } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
@Component({
selector: 'app-modern',
standalone: true
})
export class ModernComponent {
constructor() {
// ✅ Automatically unsubscribes on destroy
this.dataService.getData()
.pipe(takeUntilDestroyed())
.subscribe(data => console.log(data));
}
}
@Component({
selector: 'app-users',
standalone: true,
imports: [AsyncPipe],
template: `
@if (users$ | async; as users) {
@for (user of users; track user.id) {
<div>{{ user.name }}</div>
}
}
`
})
export class UsersComponent {
users$ = this.userService.getUsers();
// ✅ Async pipe handles subscription automatically
}

Note: Always clean up resources to prevent memory leaks.

Let’s properly manage DOM event listeners.

import { Component, ElementRef, OnDestroy, inject } from '@angular/core';
@Component({
selector: 'app-scroll-tracker',
standalone: true
})
export class ScrollTrackerComponent implements OnDestroy {
private el = inject(ElementRef);
private scrollHandler = this.onScroll.bind(this);
ngOnInit() {
// ✅ Store reference to remove later
window.addEventListener('scroll', this.scrollHandler);
}
onScroll() {
console.log('Scrolled');
}
ngOnDestroy() {
// ✅ Remove event listener
window.removeEventListener('scroll', this.scrollHandler);
}
}

Let’s manage setInterval and setTimeout properly.

@Component({
selector: 'app-timer',
standalone: true
})
export class TimerComponent implements OnDestroy {
private intervalId?: number;
private timeoutId?: number;
ngOnInit() {
// ✅ Store timer IDs
this.intervalId = window.setInterval(() => {
console.log('Tick');
}, 1000);
this.timeoutId = window.setTimeout(() => {
console.log('Delayed');
}, 5000);
}
ngOnDestroy() {
// ✅ Clear timers
if (this.intervalId) {
clearInterval(this.intervalId);
}
if (this.timeoutId) {
clearTimeout(this.timeoutId);
}
}
}

Let’s avoid holding references to removed DOM elements.

@Component({
selector: 'app-dynamic',
standalone: true
})
export class DynamicComponent implements OnDestroy {
private elementRef?: HTMLElement;
createElement() {
this.elementRef = document.createElement('div');
document.body.appendChild(this.elementRef);
}
ngOnDestroy() {
// ✅ Remove DOM element and clear reference
if (this.elementRef) {
this.elementRef.remove();
this.elementRef = undefined;
}
}
}

Let’s use WeakMap to prevent memory leaks in caches.

@Injectable({ providedIn: 'root' })
export class CacheService {
// ✅ WeakMap allows garbage collection
private cache = new WeakMap<object, any>();
set(key: object, value: any) {
this.cache.set(key, value);
}
get(key: object) {
return this.cache.get(key);
}
// When key object is garbage collected,
// the cache entry is automatically removed
}
// ✅ Good - Use takeUntilDestroyed
this.data$.pipe(takeUntilDestroyed()).subscribe();
// ✅ Good - Use async pipe
template: `{{ data$ | async }}`
// ❌ Avoid - Naked subscription
this.data$.subscribe(); // Memory leak!
// ✅ Good - Remove listeners
ngOnDestroy() {
window.removeEventListener('resize', this.handler);
}
// ❌ Avoid - Leaving listeners
ngOnDestroy() {
// No cleanup - memory leak!
}
// ✅ Good - Clear all timers
ngOnDestroy() {
clearInterval(this.intervalId);
clearTimeout(this.timeoutId);
}
// ✅ Good - Allows garbage collection
private cache = new WeakMap();
// ❌ Avoid - Prevents garbage collection
private cache = new Map();
// ✅ Good - Scoped to component
private data: Data[];
// ❌ Avoid - Global pollution
window.myData = [];
// 1. Open Chrome DevTools
// 2. Go to Memory tab
// 3. Take heap snapshot
// 4. Perform actions
// 5. Take another snapshot
// 6. Compare snapshots
// 7. Look for detached DOM nodes
// 8. Identify growing objects
// 1. Install Angular DevTools extension
// 2. Open DevTools
// 3. Go to Angular tab
// 4. Check component tree
// 5. Look for components not destroyed
// 6. Monitor change detection cycles
  • Use takeUntilDestroyed() for subscriptions
  • Prefer async pipe over manual subscriptions
  • Remove event listeners in ngOnDestroy
  • Clear all timers and intervals
  • Remove detached DOM references
  • Use WeakMap/WeakSet for caching
  • Avoid global variables
  • Profile memory usage regularly
  • Fix circular references
  • Clean up third-party library instances
  • Understand common memory leak causes
  • Use takeUntilDestroyed for subscriptions
  • Implement proper cleanup in ngOnDestroy
  • Remove event listeners correctly
  • Clear timers and intervals
  • Use WeakMap for caching
  • Profile memory with Chrome DevTools
  • Identify and fix memory leaks
  1. Performance Optimization - Optimize your applications
  2. Bundle Optimization - Reduce bundle sizes
  3. Micro Frontends - Scale your architecture

Pro Tip: Memory leaks are silent killers! Use Chrome DevTools Memory Profiler regularly to catch leaks early. Always clean up subscriptions, event listeners, and timers in ngOnDestroy! 🧠