diff --git a/src/material/bottom-sheet/bottom-sheet-ref.ts b/src/material/bottom-sheet/bottom-sheet-ref.ts index d80544ef95b..30918f1a83c 100644 --- a/src/material/bottom-sheet/bottom-sheet-ref.ts +++ b/src/material/bottom-sheet/bottom-sheet-ref.ts @@ -39,6 +39,9 @@ export class MatBottomSheetRef { /** Result to be passed down to the `afterDismissed` stream. */ private _result: R | undefined; + /** Handle to the timeout that's running as a fallback in case the exit animation doesn't fire. */ + private _closeFallbackTimeout: number; + constructor( containerInstance: MatBottomSheetContainer, private _overlayRef: OverlayRef, @@ -61,6 +64,7 @@ export class MatBottomSheetRef { containerInstance._animationStateChanged .pipe(filter(event => event.phaseName === 'done' && event.toState === 'hidden'), take(1)) .subscribe(() => { + clearTimeout(this._closeFallbackTimeout); _overlayRef.dispose(); }); @@ -91,7 +95,18 @@ export class MatBottomSheetRef { this.containerInstance._animationStateChanged.pipe( filter(event => event.phaseName === 'start'), take(1) - ).subscribe(() => this._overlayRef.detachBackdrop()); + ).subscribe(event => { + // The logic that disposes of the overlay depends on the exit animation completing, however + // it isn't guaranteed if the parent view is destroyed while it's running. Add a fallback + // timeout which will clean everything up if the animation hasn't fired within the specified + // amount of time plus 100ms. We don't need to run this outside the NgZone, because for the + // vast majority of cases the timeout will have been cleared before it has fired. + this._closeFallbackTimeout = setTimeout(() => { + this._overlayRef.dispose(); + }, event.totalTime + 100); + + this._overlayRef.detachBackdrop(); + }); this._result = result; this.containerInstance.exit(); diff --git a/src/material/bottom-sheet/bottom-sheet.spec.ts b/src/material/bottom-sheet/bottom-sheet.spec.ts index d8ec9bde934..d76f26f653f 100644 --- a/src/material/bottom-sheet/bottom-sheet.spec.ts +++ b/src/material/bottom-sheet/bottom-sheet.spec.ts @@ -192,6 +192,18 @@ describe('MatBottomSheet', () => { expect(overlayContainerElement.querySelector('mat-bottom-sheet-container')).toBeFalsy(); })); + it('should dispose of bottom sheet if view container is destroyed while animating', + fakeAsync(() => { + const bottomSheetRef = bottomSheet.open(PizzaMsg, {viewContainerRef: testViewContainerRef}); + + bottomSheetRef.dismiss(); + viewContainerFixture.detectChanges(); + viewContainerFixture.destroy(); + flush(); + + expect(overlayContainerElement.querySelector('mat-dialog-container')).toBeNull(); + })); + it('should emit the backdropClick stream when clicking on the overlay backdrop', fakeAsync(() => { const bottomSheetRef = bottomSheet.open(PizzaMsg, {viewContainerRef: testViewContainerRef}); const spy = jasmine.createSpy('backdropClick spy');