@@ -12,7 +12,6 @@ import {
12
12
ChangeDetectorRef ,
13
13
Component ,
14
14
ComponentRef ,
15
- DoCheck ,
16
15
ElementRef ,
17
16
EmbeddedViewRef ,
18
17
inject ,
@@ -51,30 +50,32 @@ import {MatSnackBarConfig} from './snack-bar-config';
51
50
imports : [ CdkPortalOutlet ] ,
52
51
host : {
53
52
'class' : 'mdc-snackbar mat-mdc-snack-bar-container' ,
54
- '[class.mat-snack-bar-animations-enabled]' : '!_animationsDisabled' ,
55
- '(animationend)' : '_animationDone($event)' ,
53
+ '[class.mat-snack-bar-container-enter]' : '_animationState === "visible"' ,
54
+ '[class.mat-snack-bar-container-exit]' : '_animationState === "hidden"' ,
55
+ '[class.mat-snack-bar-container-animations-enabled]' : '!_animationsDisabled' ,
56
+ '(animationend)' : 'onAnimationEnd($event)' ,
57
+ '(animationcancel)' : 'onAnimationEnd($event)' ,
56
58
} ,
57
59
} )
58
- export class MatSnackBarContainer extends BasePortalOutlet implements OnDestroy , DoCheck {
60
+ export class MatSnackBarContainer extends BasePortalOutlet implements OnDestroy {
59
61
private _ngZone = inject ( NgZone ) ;
60
62
private _elementRef = inject < ElementRef < HTMLElement > > ( ElementRef ) ;
61
- private _platform = inject ( Platform ) ;
62
63
private _changeDetectorRef = inject ( ChangeDetectorRef ) ;
63
- private _enterFallback : ReturnType < typeof setTimeout > | undefined ;
64
- private _exitFallback : ReturnType < typeof setTimeout > | undefined ;
64
+ private _platform = inject ( Platform ) ;
65
65
protected _animationsDisabled =
66
66
inject ( ANIMATION_MODULE_TYPE , { optional : true } ) === 'NoopAnimations' ;
67
- private _scheduleDelayedEnter : boolean ;
68
-
69
67
snackBarConfig = inject ( MatSnackBarConfig ) ;
68
+
70
69
private _document = inject ( DOCUMENT ) ;
71
70
private _trackedModals = new Set < Element > ( ) ;
71
+ private _enterFallback : ReturnType < typeof setTimeout > | undefined ;
72
+ private _exitFallback : ReturnType < typeof setTimeout > | undefined ;
72
73
73
74
/** The number of milliseconds to wait before announcing the snack bar's content. */
74
75
private readonly _announceDelay : number = 150 ;
75
76
76
77
/** The timeout for announcing the snack bar's content. */
77
- private _announceTimeoutId : ReturnType < typeof setTimeout > | undefined ;
78
+ private _announceTimeoutId : ReturnType < typeof setTimeout > ;
78
79
79
80
/** Whether the component has been destroyed. */
80
81
private _destroyed = false ;
@@ -91,6 +92,9 @@ export class MatSnackBarContainer extends BasePortalOutlet implements OnDestroy,
91
92
/** Subject for notifying that the snack bar has finished entering the view. */
92
93
readonly _onEnter : Subject < void > = new Subject ( ) ;
93
94
95
+ /** The state of the snack bar animations. */
96
+ _animationState = 'void' ;
97
+
94
98
/** aria-live value for the live region. */
95
99
_live : AriaLivePoliteness ;
96
100
@@ -167,32 +171,27 @@ export class MatSnackBarContainer extends BasePortalOutlet implements OnDestroy,
167
171
} ;
168
172
169
173
/** 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' ) {
174
+ onAnimationEnd ( event : AnimationEvent ) {
175
+ if ( event . animationName === '_mat-snack-bar-exit' ) {
174
176
this . _completeExit ( ) ;
177
+ } else if ( event . animationName === '_mat-snack-bar-enter' ) {
178
+ this . _completeEnter ( ) ;
175
179
}
176
180
}
177
181
178
182
/** Begin animation of snack bar entrance into view. */
179
183
enter ( ) : void {
180
184
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 .
185
+ this . _animationState = 'visible' ;
186
+ // _animationState lives in host bindings and `detectChanges` does not refresh host bindings
187
+ // so we have to call `markForCheck` to ensure the host view is refreshed eventually .
184
188
this . _changeDetectorRef . markForCheck ( ) ;
185
189
this . _changeDetectorRef . detectChanges ( ) ;
186
190
this . _screenReaderAnnounce ( ) ;
187
191
188
192
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 ;
193
+ queueMicrotask ( ( ) => this . _completeEnter ( ) ) ;
193
194
} else {
194
- // Guarantees that the animation-related events will
195
- // fire even if something interrupts the animation.
196
195
clearTimeout ( this . _enterFallback ) ;
197
196
this . _enterFallback = setTimeout ( ( ) => this . _completeEnter ( ) , 200 ) ;
198
197
}
@@ -201,35 +200,40 @@ export class MatSnackBarContainer extends BasePortalOutlet implements OnDestroy,
201
200
202
201
/** Begin animation of the snack bar exiting from view. */
203
202
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' , '' ) ;
203
+ // It's common for snack bars to be opened by random outside calls like HTTP requests or
204
+ // errors. Run inside the NgZone to ensure that it functions correctly.
205
+ this . _ngZone . run ( ( ) => {
206
+ // Note: this one transitions to `hidden`, rather than `void`, in order to handle the case
207
+ // where multiple snack bars are opened in quick succession (e.g. two consecutive calls to
208
+ // `MatSnackBar.open`).
209
+ this . _animationState = 'hidden' ;
210
+ this . _changeDetectorRef . markForCheck ( ) ;
208
211
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
- ) ;
212
+ // Mark this element with an 'exit' attribute to indicate that the snackbar has
213
+ // been dismissed and will soon be removed from the DOM. This is used by the snackbar
214
+ // test harness.
215
+ this . _elementRef . nativeElement . setAttribute ( 'mat-exit' , '' ) ;
216
216
217
- return this . _onExit ;
218
- }
217
+ // If the snack bar hasn't been announced by the time it exits it wouldn't have been open
218
+ // long enough to visually read it either, so clear the timeout for announcing.
219
+ clearTimeout ( this . _announceTimeoutId ) ;
219
220
220
- ngDoCheck ( ) : void {
221
- if ( this . _scheduleDelayedEnter ) {
222
- this . _scheduleDelayedEnter = false ;
223
- this . _completeEnter ( ) ;
224
- }
221
+ if ( this . _animationsDisabled ) {
222
+ queueMicrotask ( ( ) => this . _completeExit ( ) ) ;
223
+ } else {
224
+ clearTimeout ( this . _exitFallback ) ;
225
+ this . _exitFallback = setTimeout ( ( ) => this . _completeExit ( ) , 200 ) ;
226
+ }
227
+ } ) ;
228
+
229
+ return this . _onExit ;
225
230
}
226
231
232
+ /** Makes sure the exit callbacks have been invoked when the element is destroyed. */
227
233
ngOnDestroy ( ) {
228
- clearTimeout ( this . _enterFallback ) ;
229
234
this . _destroyed = true ;
230
235
this . _clearFromModals ( ) ;
231
236
this . _completeExit ( ) ;
232
- this . _onAnnounce . complete ( ) ;
233
237
}
234
238
235
239
private _completeEnter ( ) {
@@ -245,11 +249,8 @@ export class MatSnackBarContainer extends BasePortalOutlet implements OnDestroy,
245
249
* removing an element which is in the middle of an animation.
246
250
*/
247
251
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
252
clearTimeout ( this . _exitFallback ) ;
252
- this . _ngZone . run ( ( ) => {
253
+ queueMicrotask ( ( ) => {
253
254
this . _onExit . next ( ) ;
254
255
this . _onExit . complete ( ) ;
255
256
} ) ;
0 commit comments