From 6ee2c34733ecbb7d79ce32426ca61e03398816f2 Mon Sep 17 00:00:00 2001 From: crisbeto Date: Thu, 14 Sep 2017 21:59:36 +0200 Subject: [PATCH] fix(snack-bar): subsequent snack bars not opening; animation issues * Fixes subsequent snack bars not showing up if they are opened while another snack bar is open. This was due to the latter snack bar getting closed immediately, because one of the animation events coming in is `void->void`. * Simplifies the logic for determining the animation state. Using the getter ended up passing returning an invalid state in some cases (e.g. `undefined-top` or `undefined-bottom`). Fixes #7063. --- src/lib/snack-bar/snack-bar-container.ts | 24 ++++++++---------------- src/lib/snack-bar/snack-bar.spec.ts | 14 +++++++------- 2 files changed, 15 insertions(+), 23 deletions(-) diff --git a/src/lib/snack-bar/snack-bar-container.ts b/src/lib/snack-bar/snack-bar-container.ts index 0828b73cd12d..f85b2c1955aa 100644 --- a/src/lib/snack-bar/snack-bar-container.ts +++ b/src/lib/snack-bar/snack-bar-container.ts @@ -38,8 +38,6 @@ import {Subject} from 'rxjs/Subject'; import {MdSnackBarConfig} from './snack-bar-config'; -export type SnackBarState = 'visible' | 'hidden' | 'void'; - // TODO(jelbourn): we can't use constants from animation.ts here because you can't use // a text interpolation in anything that is analyzed statically with ngc (for AoT compile). export const SHOW_ANIMATION = '225ms cubic-bezier(0.4,0.0,1,1)'; @@ -59,7 +57,7 @@ export const HIDE_ANIMATION = '195ms cubic-bezier(0.0,0.0,0.2,1)'; host: { 'role': 'alert', 'class': 'mat-snack-bar-container', - '[@state]': 'getAnimationState()', + '[@state]': '_animationState', '(@state.done)': 'onAnimationEnd($event)' }, animations: [ @@ -92,7 +90,7 @@ export class MdSnackBarContainer extends BasePortalHost implements OnDestroy { _onEnter: Subject = new Subject(); /** The state of the snack bar animations. */ - private _animationState: SnackBarState; + _animationState = 'void'; /** The snack bar configuration. */ snackBarConfig: MdSnackBarConfig; @@ -105,14 +103,6 @@ export class MdSnackBarContainer extends BasePortalHost implements OnDestroy { super(); } - /** - * Gets the current animation state both combining one of the possibilities from - * SnackBarState and the vertical location. - */ - getAnimationState(): string { - return `${this._animationState}-${this.snackBarConfig.verticalPosition}`; - } - /** Attach a component portal as content to this snack bar container. */ attachComponentPortal(portal: ComponentPortal): ComponentRef { if (this._portalHost.hasAttached()) { @@ -145,11 +135,13 @@ export class MdSnackBarContainer extends BasePortalHost implements OnDestroy { /** Handle end of animations, updating the state of the snackbar. */ onAnimationEnd(event: AnimationEvent) { - if (event.toState === 'void' || event.toState.startsWith('hidden')) { + const {fromState, toState} = event; + + if ((toState === 'void' && fromState !== 'void') || toState.startsWith('hidden')) { this._completeExit(); } - if (event.toState.startsWith('visible')) { + if (toState.startsWith('visible')) { // Note: we shouldn't use `this` inside the zone callback, // because it can cause a memory leak. const onEnter = this._onEnter; @@ -164,14 +156,14 @@ export class MdSnackBarContainer extends BasePortalHost implements OnDestroy { /** Begin animation of snack bar entrance into view. */ enter(): void { if (!this._destroyed) { - this._animationState = 'visible'; + this._animationState = `visible-${this.snackBarConfig.verticalPosition}`; this._changeDetectorRef.detectChanges(); } } /** Begin animation of the snack bar exiting from view. */ exit(): Observable { - this._animationState = 'hidden'; + this._animationState = `hidden-${this.snackBarConfig.verticalPosition}`; return this._onExit; } diff --git a/src/lib/snack-bar/snack-bar.spec.ts b/src/lib/snack-bar/snack-bar.spec.ts index fdd1da844e82..4eee17634370 100644 --- a/src/lib/snack-bar/snack-bar.spec.ts +++ b/src/lib/snack-bar/snack-bar.spec.ts @@ -186,12 +186,12 @@ describe('MdSnackBar', () => { let snackBarRef = snackBar.open(simpleMessage, undefined, config); viewContainerFixture.detectChanges(); - expect(snackBarRef.containerInstance.getAnimationState()) + expect(snackBarRef.containerInstance._animationState) .toBe('visible-bottom', `Expected the animation state would be 'visible-bottom'.`); snackBarRef.dismiss(); viewContainerFixture.detectChanges(); - expect(snackBarRef.containerInstance.getAnimationState()) + expect(snackBarRef.containerInstance._animationState) .toBe('hidden-bottom', `Expected the animation state would be 'hidden-bottom'.`); }); @@ -201,7 +201,7 @@ describe('MdSnackBar', () => { snackBarRef.dismiss(); viewContainerFixture.detectChanges(); - expect(snackBarRef.containerInstance.getAnimationState()) + expect(snackBarRef.containerInstance._animationState) .toBe('hidden-bottom', `Expected the animation state would be 'hidden-bottom'.`); }); @@ -212,7 +212,7 @@ describe('MdSnackBar', () => { let dismissObservableCompleted = false; viewContainerFixture.detectChanges(); - expect(snackBarRef.containerInstance.getAnimationState()) + expect(snackBarRef.containerInstance._animationState) .toBe('visible-bottom', `Expected the animation state would be 'visible-bottom'.`); let config2 = {viewContainerRef: testViewContainerRef}; @@ -225,9 +225,9 @@ describe('MdSnackBar', () => { viewContainerFixture.whenStable().then(() => { expect(dismissObservableCompleted).toBe(true); - expect(snackBarRef.containerInstance.getAnimationState()) + expect(snackBarRef.containerInstance._animationState) .toBe('hidden-bottom', `Expected the animation state would be 'hidden-bottom'.`); - expect(snackBarRef2.containerInstance.getAnimationState()) + expect(snackBarRef2.containerInstance._animationState) .toBe('visible-bottom', `Expected the animation state would be 'visible-bottom'.`); }); })); @@ -248,7 +248,7 @@ describe('MdSnackBar', () => { // Wait for the snackbar open animation to finish. viewContainerFixture.whenStable().then(() => { - expect(snackBarRef.containerInstance.getAnimationState()) + expect(snackBarRef.containerInstance._animationState) .toBe('visible-bottom', `Expected the animation state would be 'visible-bottom'.`); }); });