In Angular, what does injecting DestroyRef offer that implementing ngOnDestroy does not, and what decides when its callbacks run?
answer
- a token, not a method
- cleanup beside its setup
- usable from helper functions
- component scope versus injector scope
- unregister function, destroyed flag
basics
~20 sngOnDestroy is one class method. DestroyRef is an injectable handle whose onDestroy() registers any number of callbacks next to their setup, even inside helper functions, and returns an unregister function; in a component it fires on that component's destruction, elsewhere on its injector's.
solid answer
~50 s`ngOnDestroy` is a method Angular calls once just before a component, directive, pipe or service instance is destroyed, so all cleanup is gathered in one place, far from the code that created it. `DestroyRef` (v16) is a token you `inject()` in an injection context. `onDestroy(callback)` can be called many times, including from a reusable helper function that injects it itself, so each setup registers its own teardown, and it returns a function that unregisters the callback. The `destroyed` getter (v20.1) lets late asynchronous code check whether the owner is gone. **Where it is injected decides its scope**: in a component or directive the callbacks run when that instance is destroyed; in a service they run when the injector that created the service is destroyed, which for `providedIn: 'root'` means when the application itself is destroyed.
code
ts · 20 linesimport { Component, DestroyRef, inject, signal } from '@angular/core';
// Reusable helper: must be called in an injection context.
function pollEvery(ms: number, tick: () => void): void {
const id = setInterval(tick, ms);
inject(DestroyRef).onDestroy(() => clearInterval(id));
}
@Component({
selector: 'app-server-clock',
template: `<time>{{ now() }}</time>`,
})
export class ServerClock {
readonly now = signal(new Date().toISOString());
constructor() {
// The interval is cleared automatically when this component is destroyed.
pollEvery(1000, () => this.now.set(new Date().toISOString()));
}
}go deeper
Recall that ngOnDestroy is a class method and DestroyRef is injected, and that onDestroy registers a cleanup callback for when the owner is destroyed.
Explain scope: component or directive destruction versus injector destruction for services, the unregister function, the destroyed getter, and the injection-context requirement.
Show how DestroyRef makes reusable helpers leak-free, how to guard late async work with destroyed, and why a root service's cleanup never runs per component.
Discuss setting a team convention for cleanup: helper functions over scattered ngOnDestroy bodies, and how that keeps leak reviews focused on setup code.
## Two ways to clean up Components acquire things that outlive them unless released: intervals, event listeners on `window`, third-party widgets, `ResizeObserver`s, open sockets. Angular offers two mechanisms for releasing them when the owner goes away. **`ngOnDestroy`** is a lifecycle hook: implement the `OnDestroy` interface and Angular calls the method once, just before the instance is destroyed. It works on components, directives and pipes, and on services, where it runs when their injector is destroyed. **`DestroyRef`**, added in v16, is a dependency-injection token. You obtain it with `inject(DestroyRef)` and call `onDestroy(callback)` on it. ## What DestroyRef adds - **Many registrations.** `onDestroy` can be called any number of times, so each piece of setup can register its own teardown right next to it instead of feeding a single growing method. - **Composability.** A plain function that calls `inject(DestroyRef)` can clean up after itself. Called from a component's constructor, it binds its cleanup to that component automatically. This is how reusable helpers stay leak-free without the caller writing any `ngOnDestroy`. - **Unregistering.** `onDestroy` returns a function; calling it removes the callback, useful when the resource is released early. - **A liveness check.** The `destroyed` getter, added in v20.1, tells asynchronous code (a promise that resolves late, a timer) whether the owner is already gone. - **Passing it around.** You can hand the `DestroyRef` instance to a class or function outside the component so it can register its own cleanup. ## What decides when callbacks run | Injected in | Callbacks run when | |---|---| | a component or directive | that component or directive is destroyed | | a service provided in a component's `providers` | that component's node injector is destroyed with it | | a `providedIn: 'root'` service | the root environment injector is destroyed, i.e. the application is torn down | | a service in an `EnvironmentInjector` you created | that injector's `destroy()` is called | The common misreading is the third row: a root service that injects `DestroyRef` does **not** clean up when the component that first used it disappears. The service outlives every component, so its cleanup is tied to the root injector. ## Rules and pitfalls 1. **Injection context only.** `inject(DestroyRef)` works in field initializers, the constructor, or functions called from them (or inside `runInInjectionContext`). In `ngOnInit` or an event handler it throws `NG0203`. Inject it early and keep the reference. 2. **Register while alive.** Calling `onDestroy` on a component's `DestroyRef` after the component was destroyed throws `NG0911` ("View has already been destroyed"). Guard late code with `destroyRef.destroyed`. 3. **No veto.** Both mechanisms notify; neither can stop or delay destruction. Angular marks the view as destroyed before running the callbacks, so `destroyed` is already `true` inside them. 4. **Mixing is fine.** A class may implement `ngOnDestroy` and also register `DestroyRef` callbacks; keep each resource's cleanup in one place rather than splitting it. 5. **Framework features build on it.** `afterNextRender`, `effect` and several RxJS interop helpers attach their own teardown to the current `DestroyRef`, which is why they need an injection context. ## Side by side | Aspect | `ngOnDestroy` | `DestroyRef` | |---|---|---| | Form | class method, `OnDestroy` interface | injectable token | | Registrations | one method per class | any number of `onDestroy` calls | | Usable from helpers | no, the helper must be told to clean up | yes, the helper injects it | | Unregister early | no | yes, via the returned function | | Liveness check | no | `destroyed` getter | | Needs injection context | no | yes, to inject it | The table is the whole argument in short: `ngOnDestroy` is simple and always available, while `DestroyRef` moves cleanup to where the resource is created and lets that code travel. ## When to use which - Use **`DestroyRef`** for new code, for cleanup that belongs to a specific setup step, and for any reusable helper. - Use **`ngOnDestroy`** where a single, obvious teardown method is clearer, or where the codebase already uses it consistently. - In both, release everything you acquired: an interval that is never cleared keeps firing, and keeps the component's closure alive, after the view is gone.
- What happens if late async code calls onDestroy on a component's DestroyRef after the component is gone?It throws `NG0911` (View has already been destroyed). Code that can resolve after the component disappears, such as a slow promise, should check `destroyRef.destroyed` first, or be cancelled by a callback registered while the component was still alive.
- When does a DestroyRef injected into a providedIn: 'root' service fire?When the root environment injector is destroyed, which in practice means the application is torn down (or a test's environment is reset). It does not fire when a component that used the service is destroyed, because the service belongs to the root injector, not to any component.
saying these in an interview costs you the question
- A root service's DestroyRef callbacks run when the component that injected it is destroyed.
- DestroyRef.onDestroy can be called only once per component.
- You can call inject(DestroyRef) inside ngOnDestroy or an event handler.
- ngOnDestroy exists only on components, never on services or pipes.
- DestroyRef callbacks run early enough to veto or delay destruction.