← Back to Articles         
When Should Cleanup Be Performed in ngOnDestroy()?

When Should Cleanup Be Performed in ngOnDestroy()?

Published on 30 Sep 2026     5 min read Angular
Component Lifecycle

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.
  • AsyncPipe handles 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.

← Back to Articles