From 03b072ce530c6b13fd3e0eab59e1fdf9a14dc4bd Mon Sep 17 00:00:00 2001 From: Nirmal Patel Date: Mon, 15 Dec 2025 12:01:39 -0800 Subject: [PATCH 1/4] Fix leak of Renderer2 instance Remove singleton pattern from PointerEventListeners. Each ResizableDirective now creates its own instance of PointerEventListeners instead of using a shared static instance. This prevents leaking of the the Renderer2 instance that the singleton holds onto when the DOM it's associated with is destroyed --- .../src/lib/resizable.directive.ts | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/projects/angular-resizable-element/src/lib/resizable.directive.ts b/projects/angular-resizable-element/src/lib/resizable.directive.ts index 5891439..ff828f4 100644 --- a/projects/angular-resizable-element/src/lib/resizable.directive.ts +++ b/projects/angular-resizable-element/src/lib/resizable.directive.ts @@ -303,7 +303,7 @@ export class ResizableDirective implements OnInit, OnDestroy { * @hidden */ constructor() { - this.pointerEventListeners = PointerEventListeners.getInstance( + this.pointerEventListeners = new PointerEventListeners( this.renderer, this.zone, ); @@ -676,21 +676,6 @@ class PointerEventListeners { public pointerUp: Observable; - private static instance: PointerEventListeners; - - public static getInstance( - renderer: Renderer2, - zone: NgZone, - ): PointerEventListeners { - if (!PointerEventListeners.instance) { - PointerEventListeners.instance = new PointerEventListeners( - renderer, - zone, - ); - } - return PointerEventListeners.instance; - } - constructor(renderer: Renderer2, zone: NgZone) { this.pointerDown = new Observable( (observer: Observer) => { From bfdcb7bf364b3d9ba48afe69e4f502943a7cc374 Mon Sep 17 00:00:00 2001 From: Nirmal Patel Date: Tue, 16 Dec 2025 09:18:33 -0800 Subject: [PATCH 2/4] Use a Renderer2 for the Document Using the injected Renderer2 leaks a reference to the host element of the first ResizableDirective instance via the singleton PointerEventListeners --- .../src/lib/resizable.directive.ts | 38 ++++++++++++++++--- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/projects/angular-resizable-element/src/lib/resizable.directive.ts b/projects/angular-resizable-element/src/lib/resizable.directive.ts index ff828f4..b7d0843 100644 --- a/projects/angular-resizable-element/src/lib/resizable.directive.ts +++ b/projects/angular-resizable-element/src/lib/resizable.directive.ts @@ -10,8 +10,10 @@ import { NgZone, PLATFORM_ID, inject, + PLATFORM_ID, + RendererFactory2 } from '@angular/core'; -import { isPlatformBrowser } from '@angular/common'; +import {DOCUMENT, isPlatformBrowser} from '@angular/common'; import { Subject, Observable, Observer, merge } from 'rxjs'; import { map, @@ -302,10 +304,21 @@ export class ResizableDirective implements OnInit, OnDestroy { /** * @hidden */ - constructor() { - this.pointerEventListeners = new PointerEventListeners( - this.renderer, - this.zone, + constructor( + @Inject(PLATFORM_ID) private platformId: any, + private renderer: Renderer2, + public elm: ElementRef, + private zone: NgZone, + rendererFactory: RendererFactory2, + @Inject(DOCUMENT) document: Document + ) { + // Create singleton instance of PointerEventListeners to avoid creating + // multiple instances of the same listener. Pass it a renderer for the + // document to avoid renderer for the host element of this directive + // instance. + this.pointerEventListeners = PointerEventListeners.getInstance( + rendererFactory.createRenderer(document, null), + zone ); } @@ -676,6 +689,21 @@ class PointerEventListeners { public pointerUp: Observable; + private static instance: PointerEventListeners; // tslint:disable-line + + public static getInstance( + renderer: Renderer2, + zone: NgZone + ): PointerEventListeners { + if (!PointerEventListeners.instance) { + PointerEventListeners.instance = new PointerEventListeners( + renderer, + zone + ); + } + return PointerEventListeners.instance; + } + constructor(renderer: Renderer2, zone: NgZone) { this.pointerDown = new Observable( (observer: Observer) => { From a3f54b7cde6149ce7b981daecb60d78c8cef4420 Mon Sep 17 00:00:00 2001 From: Nirmal Patel Date: Tue, 16 Dec 2025 12:16:43 -0800 Subject: [PATCH 3/4] Update resizable.directive.ts --- .../src/lib/resizable.directive.ts | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/projects/angular-resizable-element/src/lib/resizable.directive.ts b/projects/angular-resizable-element/src/lib/resizable.directive.ts index b7d0843..ba6d4e0 100644 --- a/projects/angular-resizable-element/src/lib/resizable.directive.ts +++ b/projects/angular-resizable-element/src/lib/resizable.directive.ts @@ -10,7 +10,6 @@ import { NgZone, PLATFORM_ID, inject, - PLATFORM_ID, RendererFactory2 } from '@angular/core'; import {DOCUMENT, isPlatformBrowser} from '@angular/common'; @@ -301,23 +300,20 @@ export class ResizableDirective implements OnInit, OnDestroy { private zone = inject(NgZone); + private rendererFactory = inject(RendererFactory2); + + private document = inject(DOCUMENT); + /** * @hidden */ - constructor( - @Inject(PLATFORM_ID) private platformId: any, - private renderer: Renderer2, - public elm: ElementRef, - private zone: NgZone, - rendererFactory: RendererFactory2, - @Inject(DOCUMENT) document: Document - ) { + constructor() { // Create singleton instance of PointerEventListeners to avoid creating // multiple instances of the same listener. Pass it a renderer for the // document to avoid renderer for the host element of this directive // instance. this.pointerEventListeners = PointerEventListeners.getInstance( - rendererFactory.createRenderer(document, null), + this.rendererFactory.createRenderer(this.document, null), zone ); } @@ -689,7 +685,7 @@ class PointerEventListeners { public pointerUp: Observable; - private static instance: PointerEventListeners; // tslint:disable-line + private static instance: PointerEventListeners; public static getInstance( renderer: Renderer2, From 461391692d8ff70d822236909d7c1e76e78d230b Mon Sep 17 00:00:00 2001 From: Matt Lewis Date: Wed, 17 Dec 2025 11:17:51 +0000 Subject: [PATCH 4/4] fix ci --- .../src/lib/resizable.directive.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/projects/angular-resizable-element/src/lib/resizable.directive.ts b/projects/angular-resizable-element/src/lib/resizable.directive.ts index ba6d4e0..1d61c67 100644 --- a/projects/angular-resizable-element/src/lib/resizable.directive.ts +++ b/projects/angular-resizable-element/src/lib/resizable.directive.ts @@ -10,9 +10,9 @@ import { NgZone, PLATFORM_ID, inject, - RendererFactory2 + RendererFactory2, } from '@angular/core'; -import {DOCUMENT, isPlatformBrowser} from '@angular/common'; +import { DOCUMENT, isPlatformBrowser } from '@angular/common'; import { Subject, Observable, Observer, merge } from 'rxjs'; import { map, @@ -314,7 +314,7 @@ export class ResizableDirective implements OnInit, OnDestroy { // instance. this.pointerEventListeners = PointerEventListeners.getInstance( this.rendererFactory.createRenderer(this.document, null), - zone + this.zone, ); } @@ -689,12 +689,12 @@ class PointerEventListeners { public static getInstance( renderer: Renderer2, - zone: NgZone + zone: NgZone, ): PointerEventListeners { if (!PointerEventListeners.instance) { PointerEventListeners.instance = new PointerEventListeners( renderer, - zone + zone, ); } return PointerEventListeners.instance;