diff --git a/src/cdk/overlay/overlay-ref.ts b/src/cdk/overlay/overlay-ref.ts index 5a604b93698a..8c6fb5831129 100644 --- a/src/cdk/overlay/overlay-ref.ts +++ b/src/cdk/overlay/overlay-ref.ts @@ -207,7 +207,7 @@ export class OverlayRef implements PortalOutlet, OverlayReference { } this._disposeScrollStrategy(); - this.detachBackdrop(); + this._disposeBackdrop(this._backdropElement); this._locationChanges.unsubscribe(); this._keyboardDispatcher.remove(this); this._portalOutlet.dispose(); @@ -419,29 +419,19 @@ export class OverlayRef implements PortalOutlet, OverlayReference { /** Detaches the backdrop (if any) associated with the overlay. */ detachBackdrop(): void { - let backdropToDetach = this._backdropElement; + const backdropToDetach = this._backdropElement; if (!backdropToDetach) { return; } let timeoutId: number; - let finishDetach = () => { + const finishDetach = () => { // It may not be attached to anything in certain cases (e.g. unit tests). if (backdropToDetach) { backdropToDetach.removeEventListener('click', this._backdropClickHandler); backdropToDetach.removeEventListener('transitionend', finishDetach); - - if (backdropToDetach.parentNode) { - backdropToDetach.parentNode.removeChild(backdropToDetach); - } - } - - // It is possible that a new portal has been attached to this overlay since we started - // removing the backdrop. If that is the case, only clear the backdrop reference if it - // is still the same instance that we started to remove. - if (this._backdropElement == backdropToDetach) { - this._backdropElement = null; + this._disposeBackdrop(backdropToDetach); } if (this._config.backdropClass) { @@ -522,6 +512,22 @@ export class OverlayRef implements PortalOutlet, OverlayReference { } } } + + /** Removes a backdrop element from the DOM. */ + private _disposeBackdrop(backdrop: HTMLElement | null) { + if (backdrop) { + if (backdrop.parentNode) { + backdrop.parentNode.removeChild(backdrop); + } + + // It is possible that a new portal has been attached to this overlay since we started + // removing the backdrop. If that is the case, only clear the backdrop reference if it + // is still the same instance that we started to remove. + if (this._backdropElement === backdrop) { + this._backdropElement = null; + } + } + } } diff --git a/src/cdk/overlay/overlay.spec.ts b/src/cdk/overlay/overlay.spec.ts index bd20090d7a7b..25c253cb200d 100644 --- a/src/cdk/overlay/overlay.spec.ts +++ b/src/cdk/overlay/overlay.spec.ts @@ -577,6 +577,28 @@ describe('Overlay', () => { expect(strategy.dispose).not.toHaveBeenCalled(); })); + it('should not throw when disposing multiple times in a row', () => { + const overlayRef = overlay.create(); + overlayRef.attach(componentPortal); + + expect(overlayContainerElement.textContent).toContain('Pizza'); + + expect(() => { + overlayRef.dispose(); + overlayRef.dispose(); + overlayRef.dispose(); + }).not.toThrow(); + }); + + it('should not trigger timers when disposing of an overlay', fakeAsync(() => { + const overlayRef = overlay.create({hasBackdrop: true}); + overlayRef.attach(templatePortal); + overlayRef.dispose(); + + // The assertion here is that `fakeAsync` doesn't flag + // any pending timeouts after the test is done. + })); + }); describe('size', () => {