Skip to content

Commit f9a638f

Browse files
committed
refactor(multiple): use renderer for manual event bindings
Switches our manual calls into `addEventListener` to use the renderer instead so that tooling (e.g. the tracing service) gets notified about them. Note that these changes only include the events that don't pass event options. The remaining events will be changed in a follow-up, because they require us to update to the latest `next` version of Angular.
1 parent 4620df1 commit f9a638f

26 files changed

+249
-209
lines changed

src/cdk/dialog/dialog-container.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import {
3333
Injector,
3434
NgZone,
3535
OnDestroy,
36+
Renderer2,
3637
ViewChild,
3738
ViewEncapsulation,
3839
afterNextRender,
@@ -79,6 +80,7 @@ export class CdkDialogContainer<C extends DialogConfig = DialogConfig>
7980
protected _ngZone = inject(NgZone);
8081
private _overlayRef = inject(OverlayRef);
8182
private _focusMonitor = inject(FocusMonitor);
83+
private _renderer = inject(Renderer2);
8284

8385
private _platform = inject(Platform);
8486
protected _document = inject(DOCUMENT, {optional: true})!;
@@ -223,13 +225,13 @@ export class CdkDialogContainer<C extends DialogConfig = DialogConfig>
223225
// The tabindex attribute should be removed to avoid navigating to that element again
224226
this._ngZone.runOutsideAngular(() => {
225227
const callback = () => {
226-
element.removeEventListener('blur', callback);
227-
element.removeEventListener('mousedown', callback);
228+
deregisterBlur();
229+
deregisterMousedown();
228230
element.removeAttribute('tabindex');
229231
};
230232

231-
element.addEventListener('blur', callback);
232-
element.addEventListener('mousedown', callback);
233+
const deregisterBlur = this._renderer.listen(element, 'blur', callback);
234+
const deregisterMousedown = this._renderer.listen(element, 'mousedown', callback);
233235
});
234236
}
235237
element.focus(options);

src/cdk/drag-drop/drag-drop.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* found in the LICENSE file at https://angular.dev/license
77
*/
88

9-
import {Injectable, NgZone, ElementRef, inject} from '@angular/core';
9+
import {Injectable, NgZone, ElementRef, inject, RendererFactory2} from '@angular/core';
1010
import {DOCUMENT} from '@angular/common';
1111
import {ViewportRuler} from '@angular/cdk/scrolling';
1212
import {DragRef, DragRefConfig} from './drag-ref';
@@ -28,6 +28,7 @@ export class DragDrop {
2828
private _ngZone = inject(NgZone);
2929
private _viewportRuler = inject(ViewportRuler);
3030
private _dragDropRegistry = inject(DragDropRegistry);
31+
private _renderer = inject(RendererFactory2).createRenderer(null, null);
3132

3233
constructor(...args: unknown[]);
3334
constructor() {}
@@ -48,6 +49,7 @@ export class DragDrop {
4849
this._ngZone,
4950
this._viewportRuler,
5051
this._dragDropRegistry,
52+
this._renderer,
5153
);
5254
}
5355

src/cdk/drag-drop/drag-ref.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
ElementRef,
2020
EmbeddedViewRef,
2121
NgZone,
22+
Renderer2,
2223
TemplateRef,
2324
ViewContainerRef,
2425
signal,
@@ -378,6 +379,7 @@ export class DragRef<T = any> {
378379
private _ngZone: NgZone,
379380
private _viewportRuler: ViewportRuler,
380381
private _dragDropRegistry: DragDropRegistry,
382+
private _renderer: Renderer2,
381383
) {
382384
this.withRootElement(element).withParent(_config.parentDragRef || null);
383385
this._parentPositions = new ParentPositionTracker(_document);
@@ -853,6 +855,7 @@ export class DragRef<T = any> {
853855
this._pickupPositionOnPage,
854856
this._initialTransform,
855857
this._config.zIndex || 1000,
858+
this._renderer,
856859
);
857860
this._preview.attach(this._getPreviewInsertionPoint(parent, shadowRoot));
858861

@@ -1106,24 +1109,24 @@ export class DragRef<T = any> {
11061109

11071110
return this._ngZone.runOutsideAngular(() => {
11081111
return new Promise(resolve => {
1109-
const handler = ((event: TransitionEvent) => {
1112+
const handler = (event: TransitionEvent) => {
11101113
if (
11111114
!event ||
11121115
(this._preview &&
11131116
_getEventTarget(event) === this._preview.element &&
11141117
event.propertyName === 'transform')
11151118
) {
1116-
this._preview?.removeEventListener('transitionend', handler);
1119+
cleanupListener();
11171120
resolve();
11181121
clearTimeout(timeout);
11191122
}
1120-
}) as EventListenerOrEventListenerObject;
1123+
};
11211124

11221125
// If a transition is short enough, the browser might not fire the `transitionend` event.
11231126
// Since we know how long it's supposed to take, add a timeout with a 50% buffer that'll
11241127
// fire if the transition hasn't completed when it was supposed to.
11251128
const timeout = setTimeout(handler as Function, duration * 1.5);
1126-
this._preview!.addEventListener('transitionend', handler);
1129+
const cleanupListener = this._preview!.addEventListener('transitionend', handler);
11271130
});
11281131
});
11291132
}

src/cdk/drag-drop/preview-ref.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* found in the LICENSE file at https://angular.dev/license
77
*/
88

9-
import {EmbeddedViewRef, TemplateRef, ViewContainerRef} from '@angular/core';
9+
import {EmbeddedViewRef, Renderer2, TemplateRef, ViewContainerRef} from '@angular/core';
1010
import {Direction} from '@angular/cdk/bidi';
1111
import {
1212
extendStyles,
@@ -56,6 +56,7 @@ export class PreviewRef {
5656
},
5757
private _initialTransform: string | null,
5858
private _zIndex: number,
59+
private _renderer: Renderer2,
5960
) {}
6061

6162
attach(parent: HTMLElement): void {
@@ -91,12 +92,8 @@ export class PreviewRef {
9192
return getTransformTransitionDurationInMs(this._preview);
9293
}
9394

94-
addEventListener(name: string, handler: EventListenerOrEventListenerObject) {
95-
this._preview.addEventListener(name, handler);
96-
}
97-
98-
removeEventListener(name: string, handler: EventListenerOrEventListenerObject) {
99-
this._preview.removeEventListener(name, handler);
95+
addEventListener(name: string, handler: (event: any) => void): () => void {
96+
return this._renderer.listen(this._preview, name, handler);
10097
}
10198

10299
private _createPreview(): HTMLElement {

src/cdk/observers/private/shared-resize-observer.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* Use of this source code is governed by an MIT-style license that can be
66
* found in the LICENSE file at https://angular.dev/license
77
*/
8-
import {inject, Injectable, NgZone, OnDestroy} from '@angular/core';
8+
import {inject, Injectable, NgZone, OnDestroy, RendererFactory2} from '@angular/core';
99
import {Observable, Subject} from 'rxjs';
1010
import {filter, shareReplay, takeUntil} from 'rxjs/operators';
1111

@@ -98,6 +98,8 @@ class SingleBoxSharedResizeObserver {
9898
providedIn: 'root',
9999
})
100100
export class SharedResizeObserver implements OnDestroy {
101+
private _cleanupErrorListener: (() => void) | undefined;
102+
101103
/** Map of box type to shared resize observer. */
102104
private _observers = new Map<ResizeObserverBoxOptions, SingleBoxSharedResizeObserver>();
103105

@@ -107,7 +109,12 @@ export class SharedResizeObserver implements OnDestroy {
107109
constructor() {
108110
if (typeof ResizeObserver !== 'undefined' && (typeof ngDevMode === 'undefined' || ngDevMode)) {
109111
this._ngZone.runOutsideAngular(() => {
110-
window.addEventListener('error', loopLimitExceededErrorHandler);
112+
const renderer = inject(RendererFactory2).createRenderer(null, null);
113+
this._cleanupErrorListener = renderer.listen(
114+
'window',
115+
'error',
116+
loopLimitExceededErrorHandler,
117+
);
111118
});
112119
}
113120
}
@@ -117,9 +124,7 @@ export class SharedResizeObserver implements OnDestroy {
117124
observer.destroy();
118125
}
119126
this._observers.clear();
120-
if (typeof ResizeObserver !== 'undefined' && (typeof ngDevMode === 'undefined' || ngDevMode)) {
121-
window.removeEventListener('error', loopLimitExceededErrorHandler);
122-
}
127+
this._cleanupErrorListener?.();
123128
}
124129

125130
/**

src/cdk/overlay/dispatchers/overlay-keyboard-dispatcher.spec.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -138,15 +138,17 @@ describe('OverlayKeyboardDispatcher', () => {
138138
it('should dispose of the global keyboard event handler correctly', () => {
139139
const overlayRef = overlay.create();
140140
const body = document.body;
141-
142141
spyOn(body, 'addEventListener');
143142
spyOn(body, 'removeEventListener');
144143

145144
keyboardDispatcher.add(overlayRef);
146-
expect(body.addEventListener).toHaveBeenCalledWith('keydown', jasmine.any(Function));
145+
expect(body.addEventListener).toHaveBeenCalledWith('keydown', jasmine.any(Function), false);
147146

148147
overlayRef.dispose();
149-
expect(body.removeEventListener).toHaveBeenCalledWith('keydown', jasmine.any(Function));
148+
expect(document.body.removeEventListener).toHaveBeenCalledWith(
149+
'keydown',
150+
jasmine.any(Function),
151+
);
150152
});
151153

152154
it('should skip overlays that do not have keydown event subscriptions', () => {

src/cdk/overlay/dispatchers/overlay-keyboard-dispatcher.ts

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* found in the LICENSE file at https://angular.dev/license
77
*/
88

9-
import {Injectable, NgZone, inject} from '@angular/core';
9+
import {Injectable, NgZone, RendererFactory2, inject} from '@angular/core';
1010
import {BaseOverlayDispatcher} from './base-overlay-dispatcher';
1111
import type {OverlayRef} from '../overlay-ref';
1212

@@ -17,30 +17,28 @@ import type {OverlayRef} from '../overlay-ref';
1717
*/
1818
@Injectable({providedIn: 'root'})
1919
export class OverlayKeyboardDispatcher extends BaseOverlayDispatcher {
20-
private _ngZone = inject(NgZone, {optional: true});
20+
private _ngZone = inject(NgZone);
21+
private _renderer = inject(RendererFactory2).createRenderer(null, null);
22+
private _cleanupKeydown: (() => void) | undefined;
2123

2224
/** Add a new overlay to the list of attached overlay refs. */
2325
override add(overlayRef: OverlayRef): void {
2426
super.add(overlayRef);
2527

2628
// Lazily start dispatcher once first overlay is added
2729
if (!this._isAttached) {
28-
/** @breaking-change 14.0.0 _ngZone will be required. */
29-
if (this._ngZone) {
30-
this._ngZone.runOutsideAngular(() =>
31-
this._document.body.addEventListener('keydown', this._keydownListener),
32-
);
33-
} else {
34-
this._document.body.addEventListener('keydown', this._keydownListener);
35-
}
30+
this._ngZone.runOutsideAngular(() => {
31+
this._cleanupKeydown = this._renderer.listen('body', 'keydown', this._keydownListener);
32+
});
33+
3634
this._isAttached = true;
3735
}
3836
}
3937

4038
/** Detaches the global keyboard event listener. */
4139
protected detach() {
4240
if (this._isAttached) {
43-
this._document.body.removeEventListener('keydown', this._keydownListener);
41+
this._cleanupKeydown?.();
4442
this._isAttached = false;
4543
}
4644
}
@@ -57,13 +55,7 @@ export class OverlayKeyboardDispatcher extends BaseOverlayDispatcher {
5755
// because we don't want overlays that don't handle keyboard events to block the ones below
5856
// them that do.
5957
if (overlays[i]._keydownEvents.observers.length > 0) {
60-
const keydownEvents = overlays[i]._keydownEvents;
61-
/** @breaking-change 14.0.0 _ngZone will be required. */
62-
if (this._ngZone) {
63-
this._ngZone.run(() => keydownEvents.next(event));
64-
} else {
65-
keydownEvents.next(event);
66-
}
58+
this._ngZone.run(() => overlays[i]._keydownEvents.next(event));
6759
break;
6860
}
6961
}

src/cdk/overlay/fullscreen-overlay-container.ts

Lines changed: 15 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* found in the LICENSE file at https://angular.dev/license
77
*/
88

9-
import {Injectable, OnDestroy} from '@angular/core';
9+
import {inject, Injectable, OnDestroy, RendererFactory2} from '@angular/core';
1010
import {OverlayContainer} from './overlay-container';
1111

1212
/**
@@ -18,8 +18,9 @@ import {OverlayContainer} from './overlay-container';
1818
*/
1919
@Injectable({providedIn: 'root'})
2020
export class FullscreenOverlayContainer extends OverlayContainer implements OnDestroy {
21+
private _renderer = inject(RendererFactory2).createRenderer(null, null);
2122
private _fullScreenEventName: string | undefined;
22-
private _fullScreenListener: () => void;
23+
private _cleanupFullScreenListener: (() => void) | undefined;
2324

2425
constructor(...args: unknown[]);
2526

@@ -29,38 +30,27 @@ export class FullscreenOverlayContainer extends OverlayContainer implements OnDe
2930

3031
override ngOnDestroy() {
3132
super.ngOnDestroy();
32-
33-
if (this._fullScreenEventName && this._fullScreenListener) {
34-
this._document.removeEventListener(this._fullScreenEventName, this._fullScreenListener);
35-
}
33+
this._cleanupFullScreenListener?.();
3634
}
3735

3836
protected override _createContainer(): void {
37+
const eventName = this._getEventName();
3938
super._createContainer();
4039
this._adjustParentForFullscreenChange();
41-
this._addFullscreenChangeListener(() => this._adjustParentForFullscreenChange());
42-
}
4340

44-
private _adjustParentForFullscreenChange(): void {
45-
if (!this._containerElement) {
46-
return;
41+
if (eventName) {
42+
this._cleanupFullScreenListener?.();
43+
this._cleanupFullScreenListener = this._renderer.listen('document', eventName, () => {
44+
this._adjustParentForFullscreenChange();
45+
});
4746
}
48-
49-
const fullscreenElement = this.getFullscreenElement();
50-
const parent = fullscreenElement || this._document.body;
51-
parent.appendChild(this._containerElement);
5247
}
5348

54-
private _addFullscreenChangeListener(fn: () => void) {
55-
const eventName = this._getEventName();
56-
57-
if (eventName) {
58-
if (this._fullScreenListener) {
59-
this._document.removeEventListener(eventName, this._fullScreenListener);
60-
}
61-
62-
this._document.addEventListener(eventName, fn);
63-
this._fullScreenListener = fn;
49+
private _adjustParentForFullscreenChange(): void {
50+
if (this._containerElement) {
51+
const fullscreenElement = this.getFullscreenElement();
52+
const parent = fullscreenElement || this._document.body;
53+
parent.appendChild(this._containerElement);
6454
}
6555
}
6656

0 commit comments

Comments
 (0)