diff --git a/src/cdk/overlay/overlay-ref.ts b/src/cdk/overlay/overlay-ref.ts index 6db556d858b7..7eced4afab2b 100644 --- a/src/cdk/overlay/overlay-ref.ts +++ b/src/cdk/overlay/overlay-ref.ts @@ -331,6 +331,7 @@ export class OverlayRef implements PortalOutlet { let backdropToDetach = this._backdropElement; if (backdropToDetach) { + let timeoutId: number; let finishDetach = () => { // It may not be attached to anything in certain cases (e.g. unit tests). if (backdropToDetach && backdropToDetach.parentNode) { @@ -343,6 +344,8 @@ export class OverlayRef implements PortalOutlet { if (this._backdropElement == backdropToDetach) { this._backdropElement = null; } + + clearTimeout(timeoutId); }; backdropToDetach.classList.remove('cdk-overlay-backdrop-showing'); @@ -351,7 +354,9 @@ export class OverlayRef implements PortalOutlet { this._toggleClasses(backdropToDetach, this._config.backdropClass, false); } - backdropToDetach.addEventListener('transitionend', finishDetach); + this._ngZone.runOutsideAngular(() => { + backdropToDetach!.addEventListener('transitionend', finishDetach); + }); // If the backdrop doesn't have a transition, the `transitionend` event won't fire. // In this case we make it unclickable and we try to remove it after a delay. @@ -360,7 +365,7 @@ export class OverlayRef implements PortalOutlet { // Run this outside the Angular zone because there's nothing that Angular cares about. // If it were to run inside the Angular zone, every test that used Overlay would have to be // either async or fakeAsync. - this._ngZone.runOutsideAngular(() => setTimeout(finishDetach, 500)); + timeoutId = this._ngZone.runOutsideAngular(() => setTimeout(finishDetach, 500)); } } diff --git a/src/cdk/overlay/overlay.spec.ts b/src/cdk/overlay/overlay.spec.ts index 0d5333faa7f9..dbcfc6b51d6a 100644 --- a/src/cdk/overlay/overlay.spec.ts +++ b/src/cdk/overlay/overlay.spec.ts @@ -9,6 +9,7 @@ import { EventEmitter, } from '@angular/core'; import {Direction, Directionality} from '@angular/cdk/bidi'; +import {dispatchFakeEvent} from '@angular/cdk/testing'; import { ComponentPortal, PortalModule, @@ -292,6 +293,19 @@ describe('Overlay', () => { expect(overlayRef.backdropElement).toBeFalsy('Expected backdrop element not to be referenced.'); })); + it('should clear the backdrop timeout if the transition finishes first', fakeAsync(() => { + const overlayRef = overlay.create({hasBackdrop: true}); + + overlayRef.attach(componentPortal); + overlayRef.detach(); + + const backdrop = overlayContainerElement.querySelector('.cdk-overlay-backdrop')!; + dispatchFakeEvent(backdrop, 'transitionend'); + + // Note: we don't `tick` or `flush` here. The assertion is that + // `fakeAsync` will throw if we have an unflushed timer. + })); + it('should be able to use the `Overlay` provider during app initialization', () => { /** Dummy provider that depends on `Overlay`. */ @Injectable()