Skip to content

Commit 266922f

Browse files
committed
fix(tooltip): clear pending timers on destroy
Currently we fire off some timers when a tooltip is shown or hidden, however if it gets destroyed before the timer has elapsed, we still end up executing some logic (change detection in this case). These changes clear the timers so that we don't end up triggering errors by accident.
1 parent 6416bab commit 266922f

File tree

3 files changed

+40
-17
lines changed

3 files changed

+40
-17
lines changed

src/material/tooltip/tooltip.spec.ts

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,17 +111,17 @@ describe('MatTooltip', () => {
111111

112112
fixture.detectChanges();
113113

114-
// wait till animation has finished
114+
// Wait until the animation has finished.
115115
tick(500);
116116

117-
// Make sure tooltip is shown to the user and animation has finished
117+
// Make sure tooltip is shown to the user and animation has finished.
118118
const tooltipElement = overlayContainerElement.querySelector('.mat-tooltip') as HTMLElement;
119119
expect(tooltipElement instanceof HTMLElement).toBe(true);
120120
expect(tooltipElement.style.transform).toBe('scale(1)');
121121

122122
expect(overlayContainerElement.textContent).toContain(initialTooltipMessage);
123123

124-
// After hide called, a timeout delay is created that will to hide the tooltip.
124+
// After hide is called, a timeout delay is created that will to hide the tooltip.
125125
const tooltipDelay = 1000;
126126
tooltipDirective.hide(tooltipDelay);
127127
expect(tooltipDirective._isTooltipVisible()).toBe(true);
@@ -719,6 +719,33 @@ describe('MatTooltip', () => {
719719
expect(overlayRef.detach).not.toHaveBeenCalled();
720720
}));
721721

722+
it('should clear the show timeout on destroy', fakeAsync(() => {
723+
assertTooltipInstance(tooltipDirective, false);
724+
725+
tooltipDirective.show(1000);
726+
fixture.detectChanges();
727+
728+
// Note that we aren't asserting anything, but `fakeAsync` will
729+
// throw if we have any timers by the end of the test.
730+
fixture.destroy();
731+
}));
732+
733+
it('should clear the hide timeout on destroy', fakeAsync(() => {
734+
assertTooltipInstance(tooltipDirective, false);
735+
736+
tooltipDirective.show();
737+
tick(0);
738+
fixture.detectChanges();
739+
tick(500);
740+
741+
tooltipDirective.hide(1000);
742+
fixture.detectChanges();
743+
744+
// Note that we aren't asserting anything, but `fakeAsync` will
745+
// throw if we have any timers by the end of the test.
746+
fixture.destroy();
747+
}));
748+
722749
});
723750

724751
describe('fallback positions', () => {

src/material/tooltip/tooltip.ts

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -550,10 +550,10 @@ export class TooltipComponent implements OnDestroy {
550550
tooltipClass: string|string[]|Set<string>|{[key: string]: any};
551551

552552
/** The timeout ID of any current timer set to show the tooltip */
553-
_showTimeoutId: number | null;
553+
_showTimeoutId: number | undefined;
554554

555555
/** The timeout ID of any current timer set to hide the tooltip */
556-
_hideTimeoutId: number | null;
556+
_hideTimeoutId: number | undefined;
557557

558558
/** Property watched by the animation framework to show or hide the tooltip */
559559
_visibility: TooltipVisibility = 'initial';
@@ -577,16 +577,13 @@ export class TooltipComponent implements OnDestroy {
577577
*/
578578
show(delay: number): void {
579579
// Cancel the delayed hide if it is scheduled
580-
if (this._hideTimeoutId) {
581-
clearTimeout(this._hideTimeoutId);
582-
this._hideTimeoutId = null;
583-
}
580+
clearTimeout(this._hideTimeoutId);
584581

585582
// Body interactions should cancel the tooltip if there is a delay in showing.
586583
this._closeOnInteraction = true;
587584
this._showTimeoutId = setTimeout(() => {
588585
this._visibility = 'visible';
589-
this._showTimeoutId = null;
586+
this._showTimeoutId = undefined;
590587

591588
// Mark for check so if any parent component has set the
592589
// ChangeDetectionStrategy to OnPush it will be checked anyways
@@ -600,14 +597,11 @@ export class TooltipComponent implements OnDestroy {
600597
*/
601598
hide(delay: number): void {
602599
// Cancel the delayed show if it is scheduled
603-
if (this._showTimeoutId) {
604-
clearTimeout(this._showTimeoutId);
605-
this._showTimeoutId = null;
606-
}
600+
clearTimeout(this._showTimeoutId);
607601

608602
this._hideTimeoutId = setTimeout(() => {
609603
this._visibility = 'hidden';
610-
this._hideTimeoutId = null;
604+
this._hideTimeoutId = undefined;
611605

612606
// Mark for check so if any parent component has set the
613607
// ChangeDetectionStrategy to OnPush it will be checked anyways
@@ -626,6 +620,8 @@ export class TooltipComponent implements OnDestroy {
626620
}
627621

628622
ngOnDestroy() {
623+
clearTimeout(this._showTimeoutId);
624+
clearTimeout(this._hideTimeoutId);
629625
this._onHide.complete();
630626
}
631627

tools/public_api_guard/material/tooltip.d.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,9 @@ export declare const SCROLL_THROTTLE_MS = 20;
6363
export declare const TOOLTIP_PANEL_CLASS = "mat-tooltip-panel";
6464

6565
export declare class TooltipComponent implements OnDestroy {
66-
_hideTimeoutId: number | null;
66+
_hideTimeoutId: number | undefined;
6767
_isHandset: Observable<BreakpointState>;
68-
_showTimeoutId: number | null;
68+
_showTimeoutId: number | undefined;
6969
_visibility: TooltipVisibility;
7070
message: string;
7171
tooltipClass: string | string[] | Set<string> | {

0 commit comments

Comments
 (0)