mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
fix(snack-bar): clean up element when associated viewContainer is destroyed (#2219)
* fix(snack-bar): clean up element when associated viewContainer is destroyed Fixes the snack bar not being removed from the DOM when it's associated `viewContainerRef` gets destroyed. Fixes #2190. * Fix bad formatting. * Rename the cleanup method.
This commit is contained in:
committed by
Jeremy Elbourn
parent
8df30dbd81
commit
db9608f875
@@ -8,7 +8,8 @@ import {
|
||||
transition,
|
||||
animate,
|
||||
AnimationTransitionEvent,
|
||||
NgZone
|
||||
NgZone,
|
||||
OnDestroy,
|
||||
} from '@angular/core';
|
||||
import {
|
||||
BasePortalHost,
|
||||
@@ -53,7 +54,7 @@ export const HIDE_ANIMATION = '195ms cubic-bezier(0.0,0.0,0.2,1)';
|
||||
])
|
||||
],
|
||||
})
|
||||
export class MdSnackBarContainer extends BasePortalHost {
|
||||
export class MdSnackBarContainer extends BasePortalHost implements OnDestroy {
|
||||
/** The portal host inside of this container into which the snack bar content will be loaded. */
|
||||
@ViewChild(PortalHostDirective) _portalHost: PortalHostDirective;
|
||||
|
||||
@@ -87,12 +88,6 @@ export class MdSnackBarContainer extends BasePortalHost {
|
||||
throw Error('Not yet implemented');
|
||||
}
|
||||
|
||||
/** Begin animation of the snack bar exiting from view. */
|
||||
exit(): Observable<void> {
|
||||
this.animationState = 'complete';
|
||||
return this.onExit.asObservable();
|
||||
}
|
||||
|
||||
/** Handle end of animations, updating the state of the snackbar. */
|
||||
onAnimationEnd(event: AnimationTransitionEvent) {
|
||||
if (event.toState === 'void' || event.toState === 'complete') {
|
||||
@@ -116,6 +111,28 @@ export class MdSnackBarContainer extends BasePortalHost {
|
||||
|
||||
/** Returns an observable resolving when the enter animation completes. */
|
||||
_onEnter(): Observable<void> {
|
||||
this.animationState = 'visible';
|
||||
return this.onEnter.asObservable();
|
||||
}
|
||||
|
||||
/** Begin animation of the snack bar exiting from view. */
|
||||
exit(): Observable<void> {
|
||||
this.animationState = 'complete';
|
||||
return this._onExit();
|
||||
}
|
||||
|
||||
/** Returns an observable that completes after the closing animation is done. */
|
||||
_onExit(): Observable<void> {
|
||||
return this.onExit.asObservable();
|
||||
}
|
||||
|
||||
/** Makes sure the exit callbacks have been invoked when the element is destroyed. */
|
||||
ngOnDestroy() {
|
||||
// Wait for the zone to settle before removing the element. Helps prevent
|
||||
// errors where we end up removing an element which is in the middle of an animation.
|
||||
this._ngZone.onMicrotaskEmpty.first().subscribe(() => {
|
||||
this.onExit.next();
|
||||
this.onExit.complete();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,16 +33,13 @@ export class MdSnackBarRef<T> {
|
||||
this.containerInstance = containerInstance;
|
||||
// Dismiss snackbar on action.
|
||||
this.onAction().subscribe(() => this.dismiss());
|
||||
containerInstance._onExit().subscribe(() => this._finishDismiss());
|
||||
}
|
||||
|
||||
/** Dismisses the snack bar. */
|
||||
dismiss(): void {
|
||||
if (!this._afterClosed.closed) {
|
||||
this.containerInstance.exit().subscribe(() => {
|
||||
this._overlayRef.dispose();
|
||||
this._afterClosed.next();
|
||||
this._afterClosed.complete();
|
||||
});
|
||||
this.containerInstance.exit();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,6 +59,13 @@ export class MdSnackBarRef<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/** Cleans up the DOM after closing. */
|
||||
private _finishDismiss(): void {
|
||||
this._overlayRef.dispose();
|
||||
this._afterClosed.next();
|
||||
this._afterClosed.complete();
|
||||
}
|
||||
|
||||
/** Gets an observable that is notified when the snack bar is finished closing. */
|
||||
afterDismissed(): Observable<void> {
|
||||
return this._afterClosed.asObservable();
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
tick,
|
||||
} from '@angular/core/testing';
|
||||
import {NgModule, Component, Directive, ViewChild, ViewContainerRef} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {MdSnackBar, MdSnackBarModule} from './snack-bar';
|
||||
import {MdSnackBarConfig} from './snack-bar-config';
|
||||
import {OverlayContainer, MdLiveAnnouncer} from '../core';
|
||||
@@ -151,6 +152,20 @@ describe('MdSnackBar', () => {
|
||||
});
|
||||
}));
|
||||
|
||||
it('should clean itself up when the view container gets destroyed', async(() => {
|
||||
snackBar.open(simpleMessage, null, { viewContainerRef: testViewContainerRef });
|
||||
viewContainerFixture.detectChanges();
|
||||
expect(overlayContainerElement.childElementCount).toBeGreaterThan(0);
|
||||
|
||||
viewContainerFixture.componentInstance.childComponentExists = false;
|
||||
viewContainerFixture.detectChanges();
|
||||
|
||||
viewContainerFixture.whenStable().then(() => {
|
||||
expect(overlayContainerElement.childElementCount)
|
||||
.toBe(0, 'Expected snack bar to be removed after the view container was destroyed');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should open a custom component', () => {
|
||||
let config = {viewContainerRef: testViewContainerRef};
|
||||
let snackBarRef = snackBar.openFromComponent(BurritosNotification, config);
|
||||
@@ -314,11 +329,13 @@ class DirectiveWithViewContainer {
|
||||
|
||||
@Component({
|
||||
selector: 'arbitrary-component',
|
||||
template: `<dir-with-view-container></dir-with-view-container>`,
|
||||
template: `<dir-with-view-container *ngIf="childComponentExists"></dir-with-view-container>`,
|
||||
})
|
||||
class ComponentWithChildViewContainer {
|
||||
@ViewChild(DirectiveWithViewContainer) childWithViewContainer: DirectiveWithViewContainer;
|
||||
|
||||
childComponentExists: boolean = true;
|
||||
|
||||
get childViewContainer() {
|
||||
return this.childWithViewContainer.viewContainerRef;
|
||||
}
|
||||
@@ -337,7 +354,7 @@ const TEST_DIRECTIVES = [ComponentWithChildViewContainer,
|
||||
BurritosNotification,
|
||||
DirectiveWithViewContainer];
|
||||
@NgModule({
|
||||
imports: [MdSnackBarModule],
|
||||
imports: [CommonModule, MdSnackBarModule],
|
||||
exports: TEST_DIRECTIVES,
|
||||
declarations: TEST_DIRECTIVES,
|
||||
entryComponents: [ComponentWithChildViewContainer, BurritosNotification],
|
||||
|
||||
Reference in New Issue
Block a user