Native browser APIs like ResizeObserver, IntersectionObserver, or third-party libraries that register listeners have no concept of Angular's component lifecycle. If a directive wires one up in the constructor without ever tearing it down, the observer retains a strong reference to the host element, preventing garbage collection even after the component is destroyed and removed from the DOM — a classic detached-node leak that grows unbounded in long-lived SPAs with frequent view churn, such as virtualized lists or router-driven dashboards. DestroyRef.onDestroy() is the modern, injectable replacement for implementing OnDestroy, and critically it works inside directives, services, and even plain functions called from an injection context, not just components.
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());}}