From 730e7ae666633688ebd7dfb0878bda348c5f0026 Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Fri, 1 Sep 2017 17:00:51 +0200 Subject: [PATCH] fix(snack-bar): animation not starting for subsequent snack bars (#6649) Since the switch to OnPush change detection, opening a snack bar while another one is open, programmatically isn't guaranteed to actually show the second snack bar, because the user might not hit the zone (e.g. if it's through a timeout). These changes trigger change detection manually on open. I also got rid of a couple of redundant methods on the snack bar container. Fixes #6222. --- src/lib/snack-bar/snack-bar-container.ts | 42 ++++++++++-------------- src/lib/snack-bar/snack-bar-ref.ts | 4 +-- 2 files changed, 20 insertions(+), 26 deletions(-) diff --git a/src/lib/snack-bar/snack-bar-container.ts b/src/lib/snack-bar/snack-bar-container.ts index 9ae28dc0cfb0..e0acfc2b1766 100644 --- a/src/lib/snack-bar/snack-bar-container.ts +++ b/src/lib/snack-bar/snack-bar-container.ts @@ -17,6 +17,7 @@ import { ElementRef, ChangeDetectionStrategy, ViewEncapsulation, + ChangeDetectorRef, } from '@angular/core'; import { trigger, @@ -63,24 +64,25 @@ export const HIDE_ANIMATION = '195ms cubic-bezier(0.0,0.0,0.2,1)'; }, animations: [ trigger('state', [ - state('void', style({transform: 'translateY(100%)'})), - state('initial', style({transform: 'translateY(100%)'})), + state('void, initial, complete', style({transform: 'translateY(100%)'})), state('visible', style({transform: 'translateY(0%)'})), - state('complete', style({transform: 'translateY(100%)'})), transition('visible => complete', animate(HIDE_ANIMATION)), transition('initial => visible, void => visible', animate(SHOW_ANIMATION)), ]) ], }) export class MdSnackBarContainer extends BasePortalHost implements OnDestroy { + /** Whether the component has been destroyed. */ + private _destroyed = false; + /** The portal host inside of this container into which the snack bar content will be loaded. */ @ViewChild(PortalHostDirective) _portalHost: PortalHostDirective; /** Subject for notifying that the snack bar has exited from view. */ - private onExit: Subject = new Subject(); + _onExit: Subject = new Subject(); /** Subject for notifying that the snack bar has finished entering the view. */ - private onEnter: Subject = new Subject(); + _onEnter: Subject = new Subject(); /** The state of the snack bar animations. */ animationState: SnackBarState = 'initial'; @@ -91,7 +93,8 @@ export class MdSnackBarContainer extends BasePortalHost implements OnDestroy { constructor( private _ngZone: NgZone, private _renderer: Renderer2, - private _elementRef: ElementRef) { + private _elementRef: ElementRef, + private _changeDetectorRef: ChangeDetectorRef) { super(); } @@ -126,7 +129,7 @@ export class MdSnackBarContainer extends BasePortalHost implements OnDestroy { if (event.toState === 'visible') { // Note: we shouldn't use `this` inside the zone callback, // because it can cause a memory leak. - const onEnter = this.onEnter; + const onEnter = this._onEnter; this._ngZone.run(() => { onEnter.next(); @@ -137,30 +140,21 @@ export class MdSnackBarContainer extends BasePortalHost implements OnDestroy { /** Begin animation of snack bar entrance into view. */ enter(): void { - this.animationState = 'visible'; - } - - /** Returns an observable resolving when the enter animation completes. */ - _onEnter(): Observable { - this.animationState = 'visible'; - return this.onEnter.asObservable(); + if (!this._destroyed) { + this.animationState = 'visible'; + this._changeDetectorRef.detectChanges(); + } } /** Begin animation of the snack bar exiting from view. */ exit(): Observable { this.animationState = 'complete'; - return this._onExit(); + return this._onExit; } - /** Returns an observable that completes after the closing animation is done. */ - _onExit(): Observable { - return this.onExit.asObservable(); - } - - /** - * Makes sure the exit callbacks have been invoked when the element is destroyed. - */ + /** Makes sure the exit callbacks have been invoked when the element is destroyed. */ ngOnDestroy() { + this._destroyed = true; this._completeExit(); } @@ -171,7 +165,7 @@ export class MdSnackBarContainer extends BasePortalHost implements OnDestroy { private _completeExit() { // Note: we shouldn't use `this` inside the zone callback, // because it can cause a memory leak. - const onExit = this.onExit; + const onExit = this._onExit; first.call(this._ngZone.onMicrotaskEmpty).subscribe(() => { onExit.next(); diff --git a/src/lib/snack-bar/snack-bar-ref.ts b/src/lib/snack-bar/snack-bar-ref.ts index a2ae2053bba0..b9a5c4dc50ee 100644 --- a/src/lib/snack-bar/snack-bar-ref.ts +++ b/src/lib/snack-bar/snack-bar-ref.ts @@ -44,7 +44,7 @@ export class MdSnackBarRef { this.containerInstance = containerInstance; // Dismiss snackbar on action. this.onAction().subscribe(() => this.dismiss()); - containerInstance._onExit().subscribe(() => this._finishDismiss()); + containerInstance._onExit.subscribe(() => this._finishDismiss()); } /** Dismisses the snack bar. */ @@ -90,7 +90,7 @@ export class MdSnackBarRef { /** Gets an observable that is notified when the snack bar has opened and appeared. */ afterOpened(): Observable { - return this.containerInstance._onEnter(); + return this.containerInstance._onEnter; } /** Gets an observable that is notified when the snack bar action is called. */