Cleanup should be performed in ngOnDestroy() when a component or directive has created or started a resource that should not continue after that component/directive is destroyed.
Simple rule: If your component starts, subscribes, registers, or opens something, and Angular does not automatically clean it up, release it in
ngOnDestroy().
Common Cleanup Scenarios
| Resource | Cleanup |
|---|---|
| RxJS subscription requiring manual cleanup | unsubscribe() |
setInterval() |
clearInterval() |
setTimeout() |
clearTimeout() |
| DOM event listener registered manually | removeEventListener() |
| WebSocket | close() |
| Third-party library instance | Call its destroy/dispose method |
| Custom external resource | Release/close it |
Full Simple Sample Program
This example starts a timer and manually registers an event listener. Both are cleaned up when the component is destroyed.
app.ts
import { Component } from '@angular/core';
import { User } from './user';
@Component({
selector: 'app-root',
standalone: true,
imports: [User],
template: `
<h1>ngOnDestroy Cleanup Example</h1>
<button (click)="showUser = !showUser">
Show / Hide User
</button>
@if (showUser) {
<app-user />
}
`
})
export class App {
showUser = true;
}
user.ts
import {
Component,
OnDestroy,
OnInit
} from '@angular/core';
@Component({
selector: 'app-user',
standalone: true,
template: `
<h2>User Component</h2>
<p>Seconds: {{ seconds }}</p>
`
})
export class User implements OnInit, OnDestroy {
seconds = 0;
private timerId?: ReturnType<typeof setInterval>;
ngOnInit(): void {
// Start timer
this.timerId = setInterval(() => {
this.seconds++;
console.log('Seconds:', this.seconds);
}, 1000);
// Register event listener
window.addEventListener(
'resize',
this.onResize
);
}
private onResize = (): void => {
console.log(
'Window width:',
window.innerWidth
);
};
ngOnDestroy(): void {
// Stop timer
if (this.timerId) {
clearInterval(this.timerId);
}
// Remove event listener
window.removeEventListener(
'resize',
this.onResize
);
console.log(
'User component cleaned up'
);
}
}
How It Works
When User is created:
User Component Created
↓
ngOnInit()
↓
Start setInterval()
↓
Register resize listener
↓
Component running...
Now click:
Show / Hide User
The condition becomes:
showUser = false;
Angular prepares to remove User.
Before removing it:
Component being removed
↓
ngOnDestroy()
↓
clearInterval()
↓
removeEventListener()
↓
Component destroyed
This is exactly where cleanup belongs.
1. Clean Up Timers
If you create:
this.timerId = setInterval(() => {
console.log('Running...');
}, 1000);
clean it up:
ngOnDestroy(): void {
if (this.timerId) {
clearInterval(this.timerId);
}
}
Otherwise, the timer may continue running even though the component is no longer needed.
2. Clean Up RxJS Subscriptions When Necessary
Traditional example:
private subscription?: Subscription;
ngOnInit(): void {
this.subscription = interval(1000)
.subscribe(value => {
console.log(value);
});
}
ngOnDestroy(): void {
this.subscription?.unsubscribe();
}
Flow:
subscribe()
↓
Observable running
↓
Component destroyed
↓
ngOnDestroy()
↓
unsubscribe()
Modern Angular
For RxJS, modern Angular often makes manual ngOnDestroy() cleanup unnecessary with:
takeUntilDestroyed()
For example:
import {
Component,
DestroyRef,
inject
} from '@angular/core';
import {
takeUntilDestroyed
} from '@angular/core/rxjs-interop';
import {
interval
} from 'rxjs';
@Component({
selector: 'app-user',
standalone: true,
template: `<h2>User</h2>`
})
export class User {
private destroyRef = inject(DestroyRef);
constructor() {
interval(1000)
.pipe(
takeUntilDestroyed(this.destroyRef)
)
.subscribe(value => {
console.log(value);
});
}
}
Angular ties the subscription to the component's destruction lifecycle.
3. Clean Up Event Listeners
If you manually register:
window.addEventListener(
'resize',
this.onResize
);
remove the same listener:
ngOnDestroy(): void {
window.removeEventListener(
'resize',
this.onResize
);
}
A useful rule:
addEventListener()
↓
removeEventListener()
setInterval()
↓
clearInterval()
subscribe()
↓
unsubscribe() ← when manual cleanup is required
WebSocket open
↓
WebSocket close
4. Close WebSocket Connections
If the component owns a WebSocket connection:
private socket =
new WebSocket('wss://example.com');
clean it up:
ngOnDestroy(): void {
this.socket.close();
}
5. Clean Up Third-Party Libraries
Suppose a chart, editor, map, or other library creates an instance:
this.editor = createEditor();
If that library provides:
destroy()
or:
dispose()
call it when the component is destroyed:
ngOnDestroy(): void {
this.editor.destroy();
}
This is especially important for libraries that create their own:
- DOM elements
- timers
- subscriptions
- event listeners
- external resources
When Is Cleanup NOT Necessary?
Not everything needs manual cleanup.
For example, Angular manages normal template event bindings:
<button (click)="save()">
Save
</button>
You don't manually remove that click listener in ngOnDestroy().
Similarly, don't blindly unsubscribe from every Observable. Some subscriptions complete naturally or are managed through Angular mechanisms such as AsyncPipe or takeUntilDestroyed().
Important Example: AsyncPipe
If your template uses:
<p>{{ user$ | async }}</p>
AsyncPipe manages its subscription lifecycle for you.
You generally don't need:
ngOnDestroy() {
// manually unsubscribe from AsyncPipe
}
Practical Decision Rule
Ask:
Will this resource continue to exist, emit, execute, listen, or consume resources after my component is gone?
If yes, and Angular/library infrastructure doesn't automatically clean it up:
Clean it up
↓
ngOnDestroy()
If it is already automatically managed:
No additional manual cleanup needed
Key Points
ngOnDestroy()runs just before Angular destroys a component/directive.- Use it to release resources owned by that component.
- Clear
setInterval()and outstanding timers when appropriate. - Remove manually registered event listeners.
- Close WebSockets and external connections.
- Unsubscribe from RxJS subscriptions when manual cleanup is required.
- Modern Angular provides
takeUntilDestroyed()for lifecycle-aware RxJS cleanup. AsyncPipehandles its own subscription cleanup.- Don't add cleanup code merely because
ngOnDestroy()exists.
Interview Answer
Cleanup should be performed in
ngOnDestroy()when a component owns resources that may remain active after the component is destroyed, such as timers, manual event listeners, WebSockets, subscriptions requiring manual unsubscription, or third-party library instances. This prevents resource leaks and unnecessary background processing.