mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
fix(dialog): DOM nodes not cleaned up if view container is destroyed mid-animation (#16309)
Currently the dialog's cleanup logic is tied to its exit animation completing. This usually isn't a problem since by default the dialog 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. Fixes #16284.
This commit is contained in:
committed by
Andrew Seguin
parent
3fb87d799c
commit
2ab867332f
@@ -42,6 +42,9 @@ export class MatDialogRef<T, R = any> {
|
||||
/** Result to be passed to afterClosed. */
|
||||
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(
|
||||
private _overlayRef: OverlayRef,
|
||||
public _containerInstance: MatDialogContainer,
|
||||
@@ -66,7 +69,10 @@ export class MatDialogRef<T, R = any> {
|
||||
_containerInstance._animationStateChanged.pipe(
|
||||
filter(event => event.phaseName === 'done' && event.toState === 'exit'),
|
||||
take(1)
|
||||
).subscribe(() => this._overlayRef.dispose());
|
||||
).subscribe(() => {
|
||||
clearTimeout(this._closeFallbackTimeout);
|
||||
this._overlayRef.dispose();
|
||||
});
|
||||
|
||||
_overlayRef.detachments().subscribe(() => {
|
||||
this._beforeClosed.next(this._result);
|
||||
@@ -99,10 +105,19 @@ export class MatDialogRef<T, R = any> {
|
||||
filter(event => event.phaseName === 'start'),
|
||||
take(1)
|
||||
)
|
||||
.subscribe(() => {
|
||||
.subscribe(event => {
|
||||
this._beforeClosed.next(dialogResult);
|
||||
this._beforeClosed.complete();
|
||||
this._overlayRef.detachBackdrop();
|
||||
|
||||
// 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 the chance to fire.
|
||||
this._closeFallbackTimeout = setTimeout(() => {
|
||||
this._overlayRef.dispose();
|
||||
}, event.totalTime + 100);
|
||||
});
|
||||
|
||||
this._containerInstance._startExitAnimation();
|
||||
|
||||
@@ -194,6 +194,17 @@ describe('MatDialog', () => {
|
||||
expect(overlayContainerElement.querySelector('mat-dialog-container')).toBeNull();
|
||||
}));
|
||||
|
||||
it('should dispose of dialog if view container is destroyed while animating', fakeAsync(() => {
|
||||
const dialogRef = dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef});
|
||||
|
||||
dialogRef.close();
|
||||
viewContainerFixture.detectChanges();
|
||||
viewContainerFixture.destroy();
|
||||
flush();
|
||||
|
||||
expect(overlayContainerElement.querySelector('mat-dialog-container')).toBeNull();
|
||||
}));
|
||||
|
||||
it('should dispatch the beforeClose and afterClose events when the ' +
|
||||
'overlay is detached externally', fakeAsync(inject([Overlay], (overlay: Overlay) => {
|
||||
const dialogRef = dialog.open(PizzaMsg, {
|
||||
@@ -1064,6 +1075,7 @@ describe('MatDialog', () => {
|
||||
|
||||
document.body.removeChild(button);
|
||||
document.body.removeChild(input);
|
||||
flush();
|
||||
}));
|
||||
|
||||
it('should move focus to the container if there are no focusable elements in the dialog',
|
||||
|
||||
Reference in New Issue
Block a user