fix(bottom-sheet): DOM nodes not cleaned up if view container is destroyed mid-animation (#16349)

Along the same lines as #16309.

Currently the bottom sheet's cleanup logic is tied to its exit animation completing. This usually isn't a problem since by default the bottom sheet is attached to the application ref, however if the consumer has set a viewContainerRef and that ref is destroyed mid-animation, the exit animation event will never fire. These changes add a timeout as a fallback in case the animation doesn't finish in the specified time.
This commit is contained in:
Kristiyan Kostadinov
2019-06-26 17:47:37 +02:00
committed by Andrew Seguin
parent 86aad590f1
commit 3fb87d799c
2 changed files with 28 additions and 1 deletions
+16 -1
View File
@@ -39,6 +39,9 @@ export class MatBottomSheetRef<T = any, R = any> {
/** 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<T = any, R = any> {
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<T = any, R = any> {
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();
@@ -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');