Skip to content

Commit 493c32d

Browse files
crisbetojelbourn
authored andcommitted
perf(tooltip): avoid triggering change detection for all keydown events (#17331)
Currently we only care about keydown events on the Escape key, while the tooltip is open, however since we're binding the listener through `host` it'll change detection for all `keydown` events.
1 parent 35b3226 commit 493c32d

File tree

2 files changed

+19
-13
lines changed

2 files changed

+19
-13
lines changed

src/material/tooltip/tooltip.ts

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -131,9 +131,6 @@ export function MAT_TOOLTIP_DEFAULT_OPTIONS_FACTORY(): MatTooltipDefaultOptions
131131
@Directive({
132132
selector: '[matTooltip]',
133133
exportAs: 'matTooltip',
134-
host: {
135-
'(keydown)': '_handleKeydown($event)',
136-
},
137134
})
138135
export class MatTooltip implements OnDestroy, OnInit {
139136
_overlayRef: OverlayRef | null;
@@ -285,6 +282,10 @@ export class MatTooltip implements OnDestroy, OnInit {
285282
_ngZone.run(() => this.show());
286283
}
287284
});
285+
286+
_ngZone.runOutsideAngular(() => {
287+
_elementRef.nativeElement.addEventListener('keydown', this._handleKeydown);
288+
});
288289
}
289290

290291
/**
@@ -299,6 +300,8 @@ export class MatTooltip implements OnDestroy, OnInit {
299300
* Dispose the tooltip when destroyed.
300301
*/
301302
ngOnDestroy() {
303+
const nativeElement = this._elementRef.nativeElement;
304+
302305
clearTimeout(this._touchstartTimeout);
303306

304307
if (this._overlayRef) {
@@ -307,16 +310,17 @@ export class MatTooltip implements OnDestroy, OnInit {
307310
}
308311

309312
// Clean up the event listeners set in the constructor
313+
nativeElement.removeEventListener('keydown', this._handleKeydown);
310314
this._passiveListeners.forEach((listener, event) => {
311-
this._elementRef.nativeElement.removeEventListener(event, listener, passiveListenerOptions);
315+
nativeElement.removeEventListener(event, listener, passiveListenerOptions);
312316
});
313317
this._passiveListeners.clear();
314318

315319
this._destroyed.next();
316320
this._destroyed.complete();
317321

318-
this._ariaDescriber.removeDescription(this._elementRef.nativeElement, this.message);
319-
this._focusMonitor.stopMonitoring(this._elementRef);
322+
this._ariaDescriber.removeDescription(nativeElement, this.message);
323+
this._focusMonitor.stopMonitoring(nativeElement);
320324
}
321325

322326
/** Shows the tooltip after the delay in ms, defaults to tooltip-delay-show or 0ms if no input */
@@ -356,12 +360,15 @@ export class MatTooltip implements OnDestroy, OnInit {
356360
return !!this._tooltipInstance && this._tooltipInstance.isVisible();
357361
}
358362

359-
/** Handles the keydown events on the host element. */
360-
_handleKeydown(e: KeyboardEvent) {
361-
if (this._isTooltipVisible() && e.keyCode === ESCAPE && !hasModifierKey(e)) {
362-
e.preventDefault();
363-
e.stopPropagation();
364-
this.hide(0);
363+
/**
364+
* Handles the keydown events on the host element.
365+
* Needs to be an arrow function so that we can use it in addEventListener.
366+
*/
367+
private _handleKeydown = (event: KeyboardEvent) => {
368+
if (this._isTooltipVisible() && event.keyCode === ESCAPE && !hasModifierKey(event)) {
369+
event.preventDefault();
370+
event.stopPropagation();
371+
this._ngZone.run(() => this.hide(0));
365372
}
366373
}
367374

tools/public_api_guard/material/tooltip.d.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ export declare class MatTooltip implements OnDestroy, OnInit {
3636
main: OverlayConnectionPosition;
3737
fallback: OverlayConnectionPosition;
3838
};
39-
_handleKeydown(e: KeyboardEvent): void;
4039
_isTooltipVisible(): boolean;
4140
hide(delay?: number): void;
4241
ngOnDestroy(): void;

0 commit comments

Comments
 (0)