@@ -51,30 +51,33 @@ import {MatSnackBarConfig} from './snack-bar-config';
51
51
imports : [ CdkPortalOutlet ] ,
52
52
host : {
53
53
'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)' ,
56
59
} ,
57
60
} )
58
- export class MatSnackBarContainer extends BasePortalOutlet implements OnDestroy , DoCheck {
61
+ export class MatSnackBarContainer extends BasePortalOutlet implements DoCheck , OnDestroy {
59
62
private _ngZone = inject ( NgZone ) ;
60
63
private _elementRef = inject < ElementRef < HTMLElement > > ( ElementRef ) ;
61
- private _platform = inject ( Platform ) ;
62
64
private _changeDetectorRef = inject ( ChangeDetectorRef ) ;
63
- private _enterFallback : ReturnType < typeof setTimeout > | undefined ;
64
- private _exitFallback : ReturnType < typeof setTimeout > | undefined ;
65
+ private _platform = inject ( Platform ) ;
65
66
protected _animationsDisabled =
66
67
inject ( ANIMATION_MODULE_TYPE , { optional : true } ) === 'NoopAnimations' ;
67
- private _scheduleDelayedEnter : boolean ;
68
-
69
68
snackBarConfig = inject ( MatSnackBarConfig ) ;
69
+
70
70
private _document = inject ( DOCUMENT ) ;
71
71
private _trackedModals = new Set < Element > ( ) ;
72
+ private _enterFallback : ReturnType < typeof setTimeout > | undefined ;
73
+ private _exitFallback : ReturnType < typeof setTimeout > | undefined ;
74
+ private _pendingNoopAnimation : boolean ;
72
75
73
76
/** The number of milliseconds to wait before announcing the snack bar's content. */
74
77
private readonly _announceDelay : number = 150 ;
75
78
76
79
/** The timeout for announcing the snack bar's content. */
77
- private _announceTimeoutId : ReturnType < typeof setTimeout > | undefined ;
80
+ private _announceTimeoutId : ReturnType < typeof setTimeout > ;
78
81
79
82
/** Whether the component has been destroyed. */
80
83
private _destroyed = false ;
@@ -91,6 +94,9 @@ export class MatSnackBarContainer extends BasePortalOutlet implements OnDestroy,
91
94
/** Subject for notifying that the snack bar has finished entering the view. */
92
95
readonly _onEnter : Subject < void > = new Subject ( ) ;
93
96
97
+ /** The state of the snack bar animations. */
98
+ _animationState = 'void' ;
99
+
94
100
/** aria-live value for the live region. */
95
101
_live : AriaLivePoliteness ;
96
102
@@ -167,32 +173,27 @@ export class MatSnackBarContainer extends BasePortalOutlet implements OnDestroy,
167
173
} ;
168
174
169
175
/** 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' ) {
174
178
this . _completeExit ( ) ;
179
+ } else if ( event . animationName === '_mat-snack-bar-enter' ) {
180
+ this . _completeEnter ( ) ;
175
181
}
176
182
}
177
183
178
184
/** Begin animation of snack bar entrance into view. */
179
185
enter ( ) : void {
180
186
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 .
184
190
this . _changeDetectorRef . markForCheck ( ) ;
185
191
this . _changeDetectorRef . detectChanges ( ) ;
186
192
this . _screenReaderAnnounce ( ) ;
187
193
188
194
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 ;
193
196
} else {
194
- // Guarantees that the animation-related events will
195
- // fire even if something interrupts the animation.
196
197
clearTimeout ( this . _enterFallback ) ;
197
198
this . _enterFallback = setTimeout ( ( ) => this . _completeEnter ( ) , 200 ) ;
198
199
}
@@ -201,35 +202,55 @@ export class MatSnackBarContainer extends BasePortalOutlet implements OnDestroy,
201
202
202
203
/** Begin animation of the snack bar exiting from view. */
203
204
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 ( ) ;
208
213
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
+ } ) ;
216
230
217
231
return this . _onExit ;
218
232
}
219
233
220
234
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
+ } ) ;
224
246
}
225
247
}
226
248
249
+ /** Makes sure the exit callbacks have been invoked when the element is destroyed. */
227
250
ngOnDestroy ( ) {
228
- clearTimeout ( this . _enterFallback ) ;
229
251
this . _destroyed = true ;
230
252
this . _clearFromModals ( ) ;
231
253
this . _completeExit ( ) ;
232
- this . _onAnnounce . complete ( ) ;
233
254
}
234
255
235
256
private _completeEnter ( ) {
@@ -245,11 +266,8 @@ export class MatSnackBarContainer extends BasePortalOutlet implements OnDestroy,
245
266
* removing an element which is in the middle of an animation.
246
267
*/
247
268
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 ) ;
251
269
clearTimeout ( this . _exitFallback ) ;
252
- this . _ngZone . run ( ( ) => {
270
+ queueMicrotask ( ( ) => {
253
271
this . _onExit . next ( ) ;
254
272
this . _onExit . complete ( ) ;
255
273
} ) ;
0 commit comments