0 Tage Serie
javascript / expert
Snippet

Speicherlecks durch DestroyRef-unwissende Drittanbieter-Listener vermeiden

Native Browser-APIs wie ResizeObserver, IntersectionObserver oder Drittanbieter-Bibliotheken, die Listener registrieren, kennen Angulars Komponenten-Lifecycle nicht. Verdrahtet eine Direktive einen solchen Observer im Konstruktor, ohne ihn je abzubauen, hält der Observer eine starke Referenz auf das Host-Element — das verhindert die Garbage Collection selbst nachdem die Komponente zerstört und aus dem DOM entfernt wurde. Das ist ein klassisches Detached-Node-Leck, das in langlebigen SPAs mit häufigem View-Wechsel, etwa virtualisierten Listen oder router-gesteuerten Dashboards, unbegrenzt wächst. DestroyRef.onDestroy() ist der moderne, injizierbare Ersatz für die Implementierung von OnDestroy und funktioniert entscheidend auch in Direktiven, Services und sogar einfachen Funktionen, die aus einem Injection-Kontext heraus aufgerufen werden — nicht nur in Komponenten.

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
import { Directive, ElementRef, inject, DestroyRef } from '@angular/core';
@Directive({
selector: '[resizeObserverFix]',
standalone: true,
})
export class ResizeObserverFixDirective {
private readonly el = inject(ElementRef<HTMLElement>);
private readonly destroyRef = inject(DestroyRef);
constructor() {
// Third-party ResizeObserver has no Angular lifecycle awareness.
const observer = new ResizeObserver((entries) => {
for (const entry of entries) {
this.el.nativeElement.dataset['width'] =
String(entry.contentRect.width);
}
});
observer.observe(this.el.nativeElement);
// Without this, the observer keeps a strong reference to a
// detached DOM node after the directive's host is destroyed.
this.destroyRef.onDestroy(() => observer.disconnect());
}
}
angular
Erklärung
1
private readonly destroyRef = inject(DestroyRef);
Injiziert das Lifecycle-Hook-Token, ohne dass die Klasse das OnDestroy-Interface implementieren muss.
2
const observer = new ResizeObserver((entries) => { ... });
Erzeugt einen nativen Observer, von dem Angulars Change Detection und Lifecycle-System nichts weiß.
3
observer.observe(this.el.nativeElement);
Stellt die starke Referenz vom Observer zum DOM-Knoten her, die explizit gelöst werden muss.
4
this.destroyRef.onDestroy(() => observer.disconnect());
Registriert Aufräumcode, der genau dann feuert, wenn Angular das Host-Element zerstört, und bricht die Referenzkette.