Skip to content

Commit db9608f

Browse files
crisbetojelbourn
authored andcommittedDec 14, 2016
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.
1 parent 8df30db commit db9608f

File tree

3 files changed

+53
-15
lines changed

3 files changed

+53
-15
lines changed
 

‎src/lib/snack-bar/snack-bar-container.ts

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ import {
88
transition,
99
animate,
1010
AnimationTransitionEvent,
11-
NgZone
11+
NgZone,
12+
OnDestroy,
1213
} from '@angular/core';
1314
import {
1415
BasePortalHost,
@@ -53,7 +54,7 @@ export const HIDE_ANIMATION = '195ms cubic-bezier(0.0,0.0,0.2,1)';
5354
])
5455
],
5556
})
56-
export class MdSnackBarContainer extends BasePortalHost {
57+
export class MdSnackBarContainer extends BasePortalHost implements OnDestroy {
5758
/** The portal host inside of this container into which the snack bar content will be loaded. */
5859
@ViewChild(PortalHostDirective) _portalHost: PortalHostDirective;
5960

@@ -87,12 +88,6 @@ export class MdSnackBarContainer extends BasePortalHost {
8788
throw Error('Not yet implemented');
8889
}
8990

90-
/** Begin animation of the snack bar exiting from view. */
91-
exit(): Observable<void> {
92-
this.animationState = 'complete';
93-
return this.onExit.asObservable();
94-
}
95-
9691
/** Handle end of animations, updating the state of the snackbar. */
9792
onAnimationEnd(event: AnimationTransitionEvent) {
9893
if (event.toState === 'void' || event.toState === 'complete') {
@@ -116,6 +111,28 @@ export class MdSnackBarContainer extends BasePortalHost {
116111

117112
/** Returns an observable resolving when the enter animation completes. */
118113
_onEnter(): Observable<void> {
114+
this.animationState = 'visible';
119115
return this.onEnter.asObservable();
120116
}
117+
118+
/** Begin animation of the snack bar exiting from view. */
119+
exit(): Observable<void> {
120+
this.animationState = 'complete';
121+
return this._onExit();
122+
}
123+
124+
/** Returns an observable that completes after the closing animation is done. */
125+
_onExit(): Observable<void> {
126+
return this.onExit.asObservable();
127+
}
128+
129+
/** Makes sure the exit callbacks have been invoked when the element is destroyed. */
130+
ngOnDestroy() {
131+
// Wait for the zone to settle before removing the element. Helps prevent
132+
// errors where we end up removing an element which is in the middle of an animation.
133+
this._ngZone.onMicrotaskEmpty.first().subscribe(() => {
134+
this.onExit.next();
135+
this.onExit.complete();
136+
});
137+
}
121138
}

‎src/lib/snack-bar/snack-bar-ref.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,16 +33,13 @@ export class MdSnackBarRef<T> {
3333
this.containerInstance = containerInstance;
3434
// Dismiss snackbar on action.
3535
this.onAction().subscribe(() => this.dismiss());
36+
containerInstance._onExit().subscribe(() => this._finishDismiss());
3637
}
3738

3839
/** Dismisses the snack bar. */
3940
dismiss(): void {
4041
if (!this._afterClosed.closed) {
41-
this.containerInstance.exit().subscribe(() => {
42-
this._overlayRef.dispose();
43-
this._afterClosed.next();
44-
this._afterClosed.complete();
45-
});
42+
this.containerInstance.exit();
4643
}
4744
}
4845

@@ -62,6 +59,13 @@ export class MdSnackBarRef<T> {
6259
}
6360
}
6461

62+
/** Cleans up the DOM after closing. */
63+
private _finishDismiss(): void {
64+
this._overlayRef.dispose();
65+
this._afterClosed.next();
66+
this._afterClosed.complete();
67+
}
68+
6569
/** Gets an observable that is notified when the snack bar is finished closing. */
6670
afterDismissed(): Observable<void> {
6771
return this._afterClosed.asObservable();

‎src/lib/snack-bar/snack-bar.spec.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
tick,
99
} from '@angular/core/testing';
1010
import {NgModule, Component, Directive, ViewChild, ViewContainerRef} from '@angular/core';
11+
import {CommonModule} from '@angular/common';
1112
import {MdSnackBar, MdSnackBarModule} from './snack-bar';
1213
import {MdSnackBarConfig} from './snack-bar-config';
1314
import {OverlayContainer, MdLiveAnnouncer} from '../core';
@@ -151,6 +152,20 @@ describe('MdSnackBar', () => {
151152
});
152153
}));
153154

155+
it('should clean itself up when the view container gets destroyed', async(() => {
156+
snackBar.open(simpleMessage, null, { viewContainerRef: testViewContainerRef });
157+
viewContainerFixture.detectChanges();
158+
expect(overlayContainerElement.childElementCount).toBeGreaterThan(0);
159+
160+
viewContainerFixture.componentInstance.childComponentExists = false;
161+
viewContainerFixture.detectChanges();
162+
163+
viewContainerFixture.whenStable().then(() => {
164+
expect(overlayContainerElement.childElementCount)
165+
.toBe(0, 'Expected snack bar to be removed after the view container was destroyed');
166+
});
167+
}));
168+
154169
it('should open a custom component', () => {
155170
let config = {viewContainerRef: testViewContainerRef};
156171
let snackBarRef = snackBar.openFromComponent(BurritosNotification, config);
@@ -314,11 +329,13 @@ class DirectiveWithViewContainer {
314329

315330
@Component({
316331
selector: 'arbitrary-component',
317-
template: `<dir-with-view-container></dir-with-view-container>`,
332+
template: `<dir-with-view-container *ngIf="childComponentExists"></dir-with-view-container>`,
318333
})
319334
class ComponentWithChildViewContainer {
320335
@ViewChild(DirectiveWithViewContainer) childWithViewContainer: DirectiveWithViewContainer;
321336

337+
childComponentExists: boolean = true;
338+
322339
get childViewContainer() {
323340
return this.childWithViewContainer.viewContainerRef;
324341
}
@@ -337,7 +354,7 @@ const TEST_DIRECTIVES = [ComponentWithChildViewContainer,
337354
BurritosNotification,
338355
DirectiveWithViewContainer];
339356
@NgModule({
340-
imports: [MdSnackBarModule],
357+
imports: [CommonModule, MdSnackBarModule],
341358
exports: TEST_DIRECTIVES,
342359
declarations: TEST_DIRECTIVES,
343360
entryComponents: [ComponentWithChildViewContainer, BurritosNotification],

0 commit comments

Comments
 (0)
Please sign in to comment.