DestroyRef made its debut in Angular 16 (commit link). It enables you to execute a callback whenever a component or directive is destroyed, or when the associated injector is disposed of.
Let’s walk through a straightforward example to see how it works in practice.
Triggering a callback on component destruction
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 snippet above emits a value every second (1000ms) and logs it to the console. While it looks harmless, it’s actually leaking memory because the subscription is never cleaned up.
Let’s address some questions you might have.
Q: What occurs if we navigate away from the current route?
A: The component gets destroyed.
Q: What happens when we navigate back to this route?
A: A new instance of the component is created.
Even though the component is gone, the subscription continues to live on.
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();
}
}
To prevent a memory leak, we need to explicitly unsubscribe. Perhaps you’re already doing this in your code?
Let’s replicate the same logic, but this time we’ll leverage
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 break down the code step by step.
- We instantiate a #destroyRef using the inject method. Keep in mind this must occur within an injection context.
- We register a callback with the onDestroy method. This function gets invoked when the component is about to be destroyed.
Alternatively, here’s another way to write the same thing:
export default class DashboardComponent {
#subscription?: Subscription;
constructor() {
this.#subscription = interval(1000).subscribe((value) => {
console.log(value);
});
inject(DestroyRef).onDestroy(() => {
this.#subscription?.unsubscribe();
});
}
}
Note: In this version, we call the
inject function inside the constructor. That’s perfectly valid since we’re still within an injection context.
However, there’s an even cleaner approach to handling unsubscription. Let’s dive into that.
TakeUntilDestroyed
Before we explore the improved unsubscription pattern, let’s review some critical 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’ve defined a custom method called
myTakeUntilDestroyed, which internally calls
inject(DestroyRef).
It’s crucial to note that the inject method cannot be used outside of an injection context.
In the example above, I invoke
myTakeUntilDestroyed from the constructor, which is perfectly safe.
Injection Context: This applies to constructors, class fields, and factory functions.
Learn more
What if we try to call this 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’re outside the injection context, Angular will raise an error.

If we absolutely need to use
myTakeUntilDestroyed inside
ngOnInit, we need to modify how we obtain
DestroyRef.
myTakeUntilDestroyed(destroyRef?: DestroyRef) {
(destroyRef ?? inject(DestroyRef)).onDestroy(() => {
this.#subscription?.unsubscribe();
});
}
With this adjustment, developers can now call
myTakeUntilDestroyed from anywhere, including outside the injection context. The updated code looks like this:
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);
}
}
We’ve now covered the essential groundwork, so we’re ready to use the
takeUntilDestroyed rxjs operator.
takeUntilDestroyed automatically completes the observable when the component/directive is destroyed or when its injector is terminated.
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
export default class DashboardComponent {
constructor() {
interval(1000)
.pipe(takeUntilDestroyed())
.subscribe((value) => {
console.log(value);
});
}
}
Excellent! We’ve achieved the same result with cleaner and more concise code.
But wait—what about the
ngOnInit hook scenario?
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 need to invoke
takeUntilDestroyed outside an injection context, it’s up to us to supply
DestroyRef manually, just like we did with our custom
myTakeUntilDestroyed function.
If you prefer video content, check out this resource that covers the exact same material:
Get To Know the Angular DestroyRef
Useful references:
Thanks for reading!