@@ -51,30 +51,33 @@ import {MatSnackBarConfig} from './snack-bar-config';
5151 imports : [ CdkPortalOutlet ] ,
5252 host : {
5353 'class' : 'mdc-snackbar mat-mdc-snack-bar-container' ,
54- '[class.mat-snack-bar-animations-enabled]' : '!_animationsDisabled' ,
55- '(animationend)' : '_animationDone($event)' ,
54+ '[class.mat-snack-bar-container-enter]' : '_animationState === "visible"' ,
55+ '[class.mat-snack-bar-container-exit]' : '_animationState === "hidden"' ,
56+ '[class.mat-snack-bar-container-animations-enabled]' : '!_animationsDisabled' ,
57+ '(animationend)' : 'onAnimationEnd($event)' ,
58+ '(animationcancel)' : 'onAnimationEnd($event)' ,
5659 } ,
5760} )
58- export class MatSnackBarContainer extends BasePortalOutlet implements OnDestroy , DoCheck {
61+ export class MatSnackBarContainer extends BasePortalOutlet implements DoCheck , OnDestroy {
5962 private _ngZone = inject ( NgZone ) ;
6063 private _elementRef = inject < ElementRef < HTMLElement > > ( ElementRef ) ;
61- private _platform = inject ( Platform ) ;
6264 private _changeDetectorRef = inject ( ChangeDetectorRef ) ;
63- private _enterFallback : ReturnType < typeof setTimeout > | undefined ;
64- private _exitFallback : ReturnType < typeof setTimeout > | undefined ;
65+ private _platform = inject ( Platform ) ;
6566 protected _animationsDisabled =
6667 inject ( ANIMATION_MODULE_TYPE , { optional : true } ) === 'NoopAnimations' ;
67- private _scheduleDelayedEnter : boolean ;
68-
6968 snackBarConfig = inject ( MatSnackBarConfig ) ;
69+
7070 private _document = inject ( DOCUMENT ) ;
7171 private _trackedModals = new Set < Element > ( ) ;
72+ private _enterFallback : ReturnType < typeof setTimeout > | undefined ;
73+ private _exitFallback : ReturnType < typeof setTimeout > | undefined ;
74+ private _pendingNoopAnimation : boolean ;
7275
7376 /** The number of milliseconds to wait before announcing the snack bar's content. */
7477 private readonly _announceDelay : number = 150 ;
7578
7679 /** The timeout for announcing the snack bar's content. */
77- private _announceTimeoutId : ReturnType < typeof setTimeout > | undefined ;
80+ private _announceTimeoutId : ReturnType < typeof setTimeout > ;
7881
7982 /** Whether the component has been destroyed. */
8083 private _destroyed = false ;
@@ -91,6 +94,9 @@ export class MatSnackBarContainer extends BasePortalOutlet implements OnDestroy,
9194 /** Subject for notifying that the snack bar has finished entering the view. */
9295 readonly _onEnter : Subject < void > = new Subject ( ) ;
9396
97+ /** The state of the snack bar animations. */
98+ _animationState = 'void' ;
99+
94100 /** aria-live value for the live region. */
95101 _live : AriaLivePoliteness ;
96102
@@ -167,32 +173,27 @@ export class MatSnackBarContainer extends BasePortalOutlet implements OnDestroy,
167173 } ;
168174
169175 /** Handle end of animations, updating the state of the snackbar. */
170- protected _animationDone ( event : AnimationEvent ) {
171- if ( event . animationName === '_mat-snack-bar-enter' ) {
172- this . _completeEnter ( ) ;
173- } else if ( event . animationName === '_mat-snack-bar-exit' ) {
176+ onAnimationEnd ( event : AnimationEvent ) {
177+ if ( event . animationName === '_mat-snack-bar-exit' ) {
174178 this . _completeExit ( ) ;
179+ } else if ( event . animationName === '_mat-snack-bar-enter' ) {
180+ this . _completeEnter ( ) ;
175181 }
176182 }
177183
178184 /** Begin animation of snack bar entrance into view. */
179185 enter ( ) : void {
180186 if ( ! this . _destroyed ) {
181- // Previously this was used to ensure that the change-detection-based animation runs.
182- // Now the animation doesn't require change detection, but there seem to be some internal
183- // usages depending on it .
187+ this . _animationState = 'visible' ;
188+ // _animationState lives in host bindings and `detectChanges` does not refresh host bindings
189+ // so we have to call `markForCheck` to ensure the host view is refreshed eventually .
184190 this . _changeDetectorRef . markForCheck ( ) ;
185191 this . _changeDetectorRef . detectChanges ( ) ;
186192 this . _screenReaderAnnounce ( ) ;
187193
188194 if ( this . _animationsDisabled ) {
189- // Delay the enter until the next change detection in an attempt to mimic the timing of
190- // the old animation-based events. Ideally we would use an `afterNextRender` here, but
191- // some tests throw a "Injector has already been destroyed" error.
192- this . _scheduleDelayedEnter = true ;
195+ this . _pendingNoopAnimation = true ;
193196 } else {
194- // Guarantees that the animation-related events will
195- // fire even if something interrupts the animation.
196197 clearTimeout ( this . _enterFallback ) ;
197198 this . _enterFallback = setTimeout ( ( ) => this . _completeEnter ( ) , 200 ) ;
198199 }
@@ -201,35 +202,55 @@ export class MatSnackBarContainer extends BasePortalOutlet implements OnDestroy,
201202
202203 /** Begin animation of the snack bar exiting from view. */
203204 exit ( ) : Observable < void > {
204- // Mark this element with an 'exit' attribute to indicate that the snackbar has
205- // been dismissed and will soon be removed from the DOM. This is used by the snackbar
206- // test harness.
207- this . _elementRef . nativeElement . setAttribute ( 'mat-exit' , '' ) ;
205+ // It's common for snack bars to be opened by random outside calls like HTTP requests or
206+ // errors. Run inside the NgZone to ensure that it functions correctly.
207+ this . _ngZone . run ( ( ) => {
208+ // Note: this one transitions to `hidden`, rather than `void`, in order to handle the case
209+ // where multiple snack bars are opened in quick succession (e.g. two consecutive calls to
210+ // `MatSnackBar.open`).
211+ this . _animationState = 'hidden' ;
212+ this . _changeDetectorRef . markForCheck ( ) ;
208213
209- // Guarantees that the animation-related events will
210- // fire even if something interrupts the animation.
211- clearTimeout ( this . _exitFallback ) ;
212- this . _exitFallback = setTimeout (
213- ( ) => this . _completeExit ( ) ,
214- this . _animationsDisabled ? undefined : 150 ,
215- ) ;
214+ // Mark this element with an 'exit' attribute to indicate that the snackbar has
215+ // been dismissed and will soon be removed from the DOM. This is used by the snackbar
216+ // test harness.
217+ this . _elementRef . nativeElement . setAttribute ( 'mat-exit' , '' ) ;
218+
219+ // If the snack bar hasn't been announced by the time it exits it wouldn't have been open
220+ // long enough to visually read it either, so clear the timeout for announcing.
221+ clearTimeout ( this . _announceTimeoutId ) ;
222+
223+ if ( this . _animationsDisabled ) {
224+ this . _pendingNoopAnimation = true ;
225+ } else {
226+ clearTimeout ( this . _exitFallback ) ;
227+ this . _exitFallback = setTimeout ( ( ) => this . _completeExit ( ) , 200 ) ;
228+ }
229+ } ) ;
216230
217231 return this . _onExit ;
218232 }
219233
220234 ngDoCheck ( ) : void {
221- if ( this . _scheduleDelayedEnter ) {
222- this . _scheduleDelayedEnter = false ;
223- this . _completeEnter ( ) ;
235+ // Aims to mimic the timing of when the snack back was using the animations
236+ // module since many internal tests depend on the old timing.
237+ if ( this . _pendingNoopAnimation ) {
238+ this . _pendingNoopAnimation = false ;
239+ queueMicrotask ( ( ) => {
240+ if ( this . _animationState === 'visible' ) {
241+ this . _completeEnter ( ) ;
242+ } else {
243+ this . _completeExit ( ) ;
244+ }
245+ } ) ;
224246 }
225247 }
226248
249+ /** Makes sure the exit callbacks have been invoked when the element is destroyed. */
227250 ngOnDestroy ( ) {
228- clearTimeout ( this . _enterFallback ) ;
229251 this . _destroyed = true ;
230252 this . _clearFromModals ( ) ;
231253 this . _completeExit ( ) ;
232- this . _onAnnounce . complete ( ) ;
233254 }
234255
235256 private _completeEnter ( ) {
@@ -245,11 +266,8 @@ export class MatSnackBarContainer extends BasePortalOutlet implements OnDestroy,
245266 * removing an element which is in the middle of an animation.
246267 */
247268 private _completeExit ( ) {
248- // If the snack bar hasn't been announced by the time it exits it wouldn't have been open
249- // long enough to visually read it either, so clear the timeout for announcing.
250- clearTimeout ( this . _announceTimeoutId ) ;
251269 clearTimeout ( this . _exitFallback ) ;
252- this . _ngZone . run ( ( ) => {
270+ queueMicrotask ( ( ) => {
253271 this . _onExit . next ( ) ;
254272 this . _onExit . complete ( ) ;
255273 } ) ;
0 commit comments