Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 25 additions & 8 deletions src/lib/snack-bar/snack-bar-container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import {
transition,
animate,
AnimationTransitionEvent,
NgZone
NgZone,
OnDestroy,
} from '@angular/core';
import {
BasePortalHost,
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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') {
Expand All @@ -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();
});
}
}
14 changes: 9 additions & 5 deletions src/lib/snack-bar/snack-bar-ref.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}

Expand All @@ -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();
Expand Down
21 changes: 19 additions & 2 deletions src/lib/snack-bar/snack-bar.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
}
Expand All @@ -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],
Expand Down