DestroyRef has been introduced in Angular 16 (commit link). It gives us the option to run a callback function when the component/directive is destroyed or when the corresponding injector is destroyed.
Let’s see an easy example to understand how we can use that.
Callback when a component is being destroyed
import { Component } from '@angular/core';
import { interval } from 'rxjs';
@Component({
  selector: 'app-dashboard',
  standalone: true,
  template: ``,
})
export default class DashboardComponent {
  constructor() {
    interval(1000).subscribe((value) => {
      console.log(value);
    });
  }
}The code above emits a new value every 1 sec (1000ms) and logs a value to the console. It’s a small piece of code, but it still creates a memory leak since we are not destroying the subscription.
Let’s answer some questions you may have.
Q: What would happen if we changed the route?
A: Well, the component would be destroyed.
Q: What would happen if we came back to this route?
A: Well, the component would be constructed again.
Despite the component being destroyed, the subscription remains active.
import { Component, OnDestroy } from '@angular/core';
import { Subscription, interval } from 'rxjs';
@Component({
  selector: 'app-dashboard',
  standalone: true,
  template: ``,
})
export default class DashboardComponent implements OnDestroy {
  #subscription?: Subscription;
  constructor() {
    this.#subscription = interval(1000).subscribe((value) => {
      console.log(value);
    });
  }
  
  ngOnDestroy(): void {
    this.#subscription?.unsubscribe();
  }
}We have to unsubscribe the subscription to avoid creating a memory leak. But perhaps you are already doing this ?
Let’s do the same, but this time using `DestroyRef`
import { Component, DestroyRef, inject } from '@angular/core';
import { Subscription, interval } from 'rxjs';
@Component({
  selector: 'app-dashboard',
  standalone: true,
  template: ``,
})
export default class DashboardComponent {
  #subscription?: Subscription;
  #destroyRef = inject(DestroyRef);
  constructor() {
    this.#subscription = interval(1000).subscribe((value) => {
      console.log(value);
    });
    this.#destroyRef.onDestroy(() => {
      this.#subscription?.unsubscribe();
    });
  }
}Let’s read the code from top to bottom.
- We are creating a #destroyRef instance using the inject method. Please note that this is happening during the injection context.
- We are registering a callback function in the onDestroy method. The given function will be executed when the component is being destroyed.
Alternatively, we could write the same piece of code like that:
export default class DashboardComponent {
  #subscription?: Subscription;
  constructor() {
    this.#subscription = interval(1000).subscribe((value) => {
      console.log(value);
    });
    inject(DestroyRef).onDestroy(() => {
      this.#subscription?.unsubscribe();
    });
  }
}Note: This time, we are using the inject function in the constructor. This still works fine since we are in the injection context.
There is a better way to unsubscribe, though. Keep reading 🙂
TakeUntilDestroyed
Before we look at a better way to unsubscribe, let’s dig into some important details.
export default class DashboardComponent {
  #subscription?: Subscription;
  myTakeUntilDestroyed() {
    inject(DestroyRef).onDestroy(() => {
      this.#subscription?.unsubscribe();
    });
  }
  constructor() {
    this.#subscription = interval(1000).subscribe((value) => {
      console.log(value);
    });
    this.myTakeUntilDestroyed();
  }
}I have created the method `myTakeUntilDestroyed`, which injects `DestroyRef`.
It’s important to understand that we cannot use the inject method outside the injection context.
In the example above, I call `myTakeUntilDestroyed` from the constructor, which works fine.
Injection Context: Constructor, class fields, factory method. Read more
What would happen if we call the method from the `ngOnInit` hook?
export default class DashboardComponent implements OnInit {
  #subscription?: Subscription;
  myTakeUntilDestroyed() {
    inject(DestroyRef).onDestroy(() => {
      this.#subscription?.unsubscribe();
    });
  }
  constructor() {
    this.#subscription = interval(1000).subscribe((value) => {
      console.log(value);
    });
  }
  ngOnInit(): void {
    this.myTakeUntilDestroyed();
  }
}Since we are not in the injection context, Angular will throw an error.

If we, however, have to call `myTakeUntilDestroyed` from the `ngOnInit` hook, we should change how we access `DestroyRef`.
myTakeUntilDestroyed(destroyRef?: DestroyRef) {
    (destroyRef ?? inject(DestroyRef)).onDestroy(() => {
      this.#subscription?.unsubscribe();
    });
  }This change allows the developer to use `myTakeUntilDestroyed` outside of the injection context. As such, the code will become:
export default class DashboardComponent implements OnInit {
  #subscription?: Subscription;
  #destroyRef = inject(DestroyRef);
  myTakeUntilDestroyed(destroyRef?: DestroyRef) {
    (destroyRef ?? inject(DestroyRef)).onDestroy(() => {
      this.#subscription?.unsubscribe();
    });
  }
  constructor() {
    this.#subscription = interval(1000).subscribe((value) => {
      console.log(value);
    });
  }
  ngOnInit(): void {
    this.myTakeUntilDestroyed(this.#destroyRef);
  }
}So far, we have covered some important details, and we are now ready to start using the `takeUntilDestroyed` rxjs operator.
takeUntilDestroyed completes the observable when the component/directive is destroyed or when the corresponding injector is destroyed!
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
export default class DashboardComponent {
  constructor() {
    interval(1000)
      .pipe(takeUntilDestroyed())
      .subscribe((value) => {
        console.log(value);
      });
  }
}That’s great! We have achieved the same with less and easy-to-read code. Nice!
Oh, wait, how about the `ngOnInit` hook?
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
export default class DashboardComponent implements OnInit {
  #destroyRef = inject(DestroyRef);
  ngOnInit(): void {
    interval(1000)
      .pipe(takeUntilDestroyed(this.#destroyRef))
      .subscribe((value) => {
        console.log(value);
      });
  }
}If we have to use the `takeUntilDestroyed` operator outside the injection context, we (the developers) are responsible for providing `DestroyRef`, similar as in our custom myTakeUntilDestroyed method.
If you enjoy watching videos, you must take a look at this one that covers the same content as the article
Get To Know the Angular DestroyRef
Useful links:
Thanks for reading my article!
