Skip to content

Commit 70ff528

Browse files
authored
fix(material/tooltip): clear pending timers on destroy (#16756)
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 1a10027 commit 70ff528

File tree

4 files changed

+67
-17
lines changed

4 files changed

+67
-17
lines changed

src/material-experimental/mdc-tooltip/tooltip.spec.ts

+27
Original file line numberDiff line numberDiff line change
@@ -825,6 +825,33 @@ describe('MDC-based MatTooltip', () => {
825825
expect(classList).toContain('mat-tooltip-panel-left');
826826
}));
827827

828+
it('should clear the show timeout on destroy', fakeAsync(() => {
829+
assertTooltipInstance(tooltipDirective, false);
830+
831+
tooltipDirective.show(1000);
832+
fixture.detectChanges();
833+
834+
// Note that we aren't asserting anything, but `fakeAsync` will
835+
// throw if we have any timers by the end of the test.
836+
fixture.destroy();
837+
}));
838+
839+
it('should clear the hide timeout on destroy', fakeAsync(() => {
840+
assertTooltipInstance(tooltipDirective, false);
841+
842+
tooltipDirective.show();
843+
tick(0);
844+
fixture.detectChanges();
845+
tick(500);
846+
847+
tooltipDirective.hide(1000);
848+
fixture.detectChanges();
849+
850+
// Note that we aren't asserting anything, but `fakeAsync` will
851+
// throw if we have any timers by the end of the test.
852+
fixture.destroy();
853+
}));
854+
828855
});
829856

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

src/material/tooltip/tooltip.spec.ts

+30-3
Original file line numberDiff line numberDiff line change
@@ -113,17 +113,17 @@ describe('MatTooltip', () => {
113113

114114
fixture.detectChanges();
115115

116-
// wait till animation has finished
116+
// Wait until the animation has finished.
117117
tick(500);
118118

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

124124
expect(overlayContainerElement.textContent).toContain(initialTooltipMessage);
125125

126-
// After hide called, a timeout delay is created that will to hide the tooltip.
126+
// After hide is called, a timeout delay is created that will to hide the tooltip.
127127
const tooltipDelay = 1000;
128128
tooltipDirective.hide(tooltipDelay);
129129
expect(tooltipDirective._isTooltipVisible()).toBe(true);
@@ -824,6 +824,33 @@ describe('MatTooltip', () => {
824824
expect(classList).toContain('mat-tooltip-panel-left');
825825
}));
826826

827+
it('should clear the show timeout on destroy', fakeAsync(() => {
828+
assertTooltipInstance(tooltipDirective, false);
829+
830+
tooltipDirective.show(1000);
831+
fixture.detectChanges();
832+
833+
// Note that we aren't asserting anything, but `fakeAsync` will
834+
// throw if we have any timers by the end of the test.
835+
fixture.destroy();
836+
}));
837+
838+
it('should clear the hide timeout on destroy', fakeAsync(() => {
839+
assertTooltipInstance(tooltipDirective, false);
840+
841+
tooltipDirective.show();
842+
tick(0);
843+
fixture.detectChanges();
844+
tick(500);
845+
846+
tooltipDirective.hide(1000);
847+
fixture.detectChanges();
848+
849+
// Note that we aren't asserting anything, but `fakeAsync` will
850+
// throw if we have any timers by the end of the test.
851+
fixture.destroy();
852+
}));
853+
827854
});
828855

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

src/material/tooltip/tooltip.ts

+8-12
Original file line numberDiff line numberDiff line change
@@ -764,10 +764,10 @@ export abstract class _TooltipComponentBase implements OnDestroy {
764764
tooltipClass: string|string[]|Set<string>|{[key: string]: any};
765765

766766
/** The timeout ID of any current timer set to show the tooltip */
767-
_showTimeoutId: number | null;
767+
_showTimeoutId: number | undefined;
768768

769769
/** The timeout ID of any current timer set to hide the tooltip */
770-
_hideTimeoutId: number | null;
770+
_hideTimeoutId: number | undefined;
771771

772772
/** Property watched by the animation framework to show or hide the tooltip */
773773
_visibility: TooltipVisibility = 'initial';
@@ -786,16 +786,13 @@ export abstract class _TooltipComponentBase implements OnDestroy {
786786
*/
787787
show(delay: number): void {
788788
// Cancel the delayed hide if it is scheduled
789-
if (this._hideTimeoutId) {
790-
clearTimeout(this._hideTimeoutId);
791-
this._hideTimeoutId = null;
792-
}
789+
clearTimeout(this._hideTimeoutId);
793790

794791
// Body interactions should cancel the tooltip if there is a delay in showing.
795792
this._closeOnInteraction = true;
796793
this._showTimeoutId = setTimeout(() => {
797794
this._visibility = 'visible';
798-
this._showTimeoutId = null;
795+
this._showTimeoutId = undefined;
799796

800797
// Mark for check so if any parent component has set the
801798
// ChangeDetectionStrategy to OnPush it will be checked anyways
@@ -809,14 +806,11 @@ export abstract class _TooltipComponentBase implements OnDestroy {
809806
*/
810807
hide(delay: number): void {
811808
// Cancel the delayed show if it is scheduled
812-
if (this._showTimeoutId) {
813-
clearTimeout(this._showTimeoutId);
814-
this._showTimeoutId = null;
815-
}
809+
clearTimeout(this._showTimeoutId);
816810

817811
this._hideTimeoutId = setTimeout(() => {
818812
this._visibility = 'hidden';
819-
this._hideTimeoutId = null;
813+
this._hideTimeoutId = undefined;
820814

821815
// Mark for check so if any parent component has set the
822816
// ChangeDetectionStrategy to OnPush it will be checked anyways
@@ -835,6 +829,8 @@ export abstract class _TooltipComponentBase implements OnDestroy {
835829
}
836830

837831
ngOnDestroy() {
832+
clearTimeout(this._showTimeoutId);
833+
clearTimeout(this._hideTimeoutId);
838834
this._onHide.complete();
839835
}
840836

tools/public_api_guard/material/tooltip.d.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ export declare abstract class _MatTooltipBase<T extends _TooltipComponentBase> i
4545
}
4646

4747
export declare abstract class _TooltipComponentBase implements OnDestroy {
48-
_hideTimeoutId: number | null;
49-
_showTimeoutId: number | null;
48+
_hideTimeoutId: number | undefined;
49+
_showTimeoutId: number | undefined;
5050
_visibility: TooltipVisibility;
5151
message: string;
5252
tooltipClass: string | string[] | Set<string> | {

0 commit comments

Comments
 (0)