0 day streak
javascript / expert
Snippet

Deterministic Teardown Ordering in Nested DestroyRef Hierarchies

`DestroyRef.onDestroy` callbacks run in registration order per injector, but resources registered dynamically at runtime — sockets opened by lazy-loaded feature components, for example — need reverse-of-acquisition teardown to avoid closing a shared dependency before the handle that still references it. This pool tracks insertion order in a `Map` and tears down in reverse, and the `closing` flag closes anything registered mid-teardown immediately instead of leaking it into a set that will never be iterated again. Without the flag, a component destroyed by its own `ngOnDestroy` during the pool's teardown loop could register a handle that silently never gets closed.

snippet.js
javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import { Injectable, DestroyRef, inject } from '@angular/core';
@Injectable()
export class ResourcePool {
private handles = new Map<string, { close(): void }>();
private closing = false;
private destroyRef = inject(DestroyRef);
constructor() {
this.destroyRef.onDestroy(() => this.teardown());
}
register(key: string, handle: { close(): void }): void {
if (this.closing) {
handle.close();
return;
}
this.handles.get(key)?.close();
this.handles.set(key, handle);
}
private teardown(): void {
this.closing = true;
for (const [key, handle] of [...this.handles].reverse()) {
handle.close();
this.handles.delete(key);
}
}
}
angular
Breakdown
1
this.destroyRef.onDestroy(() => this.teardown());
Ties cleanup to the injector's own lifecycle rather than a component's `ngOnDestroy`, so the pool tears down correctly even when provided at a route or lazy-loaded injector level.
2
for (const [key, handle] of [...this.handles].reverse()) {
Snapshots entries into an array before reversing, since mutating a `Map` during iteration over its own live iterator has undefined-order pitfalls.
3
if (this.closing) { handle.close(); return; }
Catches registrations that arrive after teardown started, closing them immediately so they never sit unclosed in a `Map` nobody will drain again.