Skip to content

Commit 73a409b

Browse files
committed
fixup! fix(material/snack-bar): switch away from animations module
1 parent f2a3fc3 commit 73a409b

File tree

3 files changed

+59
-57
lines changed

3 files changed

+59
-57
lines changed

src/material/snack-bar/snack-bar-container.scss

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,15 @@ $_side-padding: 8px;
4242
}
4343
}
4444

45-
.mat-snack-bar-animations-enabled {
46-
animation: _mat-snack-bar-enter 150ms cubic-bezier(0, 0, 0.2, 1);
45+
.mat-snack-bar-container-animations-enabled {
46+
opacity: 0;
4747

48-
&[mat-exit] {
49-
animation: _mat-snack-bar-exit 75ms cubic-bezier(0.4, 0, 1, 1);
48+
&.mat-snack-bar-container-enter {
49+
animation: _mat-snack-bar-enter 150ms cubic-bezier(0, 0, 0.2, 1) forwards;
50+
}
51+
52+
&.mat-snack-bar-container-exit {
53+
animation: _mat-snack-bar-exit 75ms cubic-bezier(0.4, 0, 1, 1) forwards;
5054
}
5155
}
5256

src/material/snack-bar/snack-bar-container.ts

Lines changed: 48 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import {
1212
ChangeDetectorRef,
1313
Component,
1414
ComponentRef,
15-
DoCheck,
1615
ElementRef,
1716
EmbeddedViewRef,
1817
inject,
@@ -51,30 +50,32 @@ import {MatSnackBarConfig} from './snack-bar-config';
5150
imports: [CdkPortalOutlet],
5251
host: {
5352
'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)',
5658
},
5759
})
58-
export class MatSnackBarContainer extends BasePortalOutlet implements OnDestroy, DoCheck {
60+
export class MatSnackBarContainer extends BasePortalOutlet implements OnDestroy {
5961
private _ngZone = inject(NgZone);
6062
private _elementRef = inject<ElementRef<HTMLElement>>(ElementRef);
61-
private _platform = inject(Platform);
6263
private _changeDetectorRef = inject(ChangeDetectorRef);
63-
private _enterFallback: ReturnType<typeof setTimeout> | undefined;
64-
private _exitFallback: ReturnType<typeof setTimeout> | undefined;
64+
private _platform = inject(Platform);
6565
protected _animationsDisabled =
6666
inject(ANIMATION_MODULE_TYPE, {optional: true}) === 'NoopAnimations';
67-
private _scheduleDelayedEnter: boolean;
68-
6967
snackBarConfig = inject(MatSnackBarConfig);
68+
7069
private _document = inject(DOCUMENT);
7170
private _trackedModals = new Set<Element>();
71+
private _enterFallback: ReturnType<typeof setTimeout> | undefined;
72+
private _exitFallback: ReturnType<typeof setTimeout> | undefined;
7273

7374
/** The number of milliseconds to wait before announcing the snack bar's content. */
7475
private readonly _announceDelay: number = 150;
7576

7677
/** The timeout for announcing the snack bar's content. */
77-
private _announceTimeoutId: ReturnType<typeof setTimeout> | undefined;
78+
private _announceTimeoutId: ReturnType<typeof setTimeout>;
7879

7980
/** Whether the component has been destroyed. */
8081
private _destroyed = false;
@@ -91,6 +92,9 @@ export class MatSnackBarContainer extends BasePortalOutlet implements OnDestroy,
9192
/** Subject for notifying that the snack bar has finished entering the view. */
9293
readonly _onEnter: Subject<void> = new Subject();
9394

95+
/** The state of the snack bar animations. */
96+
_animationState = 'void';
97+
9498
/** aria-live value for the live region. */
9599
_live: AriaLivePoliteness;
96100

@@ -167,32 +171,27 @@ export class MatSnackBarContainer extends BasePortalOutlet implements OnDestroy,
167171
};
168172

169173
/** 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') {
174176
this._completeExit();
177+
} else if (event.animationName === '_mat-snack-bar-enter') {
178+
this._completeEnter();
175179
}
176180
}
177181

178182
/** Begin animation of snack bar entrance into view. */
179183
enter(): void {
180184
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.
184188
this._changeDetectorRef.markForCheck();
185189
this._changeDetectorRef.detectChanges();
186190
this._screenReaderAnnounce();
187191

188192
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());
193194
} else {
194-
// Guarantees that the animation-related events will
195-
// fire even if something interrupts the animation.
196195
clearTimeout(this._enterFallback);
197196
this._enterFallback = setTimeout(() => this._completeEnter(), 200);
198197
}
@@ -201,35 +200,40 @@ export class MatSnackBarContainer extends BasePortalOutlet implements OnDestroy,
201200

202201
/** Begin animation of the snack bar exiting from view. */
203202
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();
208211

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', '');
216216

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);
219220

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;
225230
}
226231

232+
/** Makes sure the exit callbacks have been invoked when the element is destroyed. */
227233
ngOnDestroy() {
228-
clearTimeout(this._enterFallback);
229234
this._destroyed = true;
230235
this._clearFromModals();
231236
this._completeExit();
232-
this._onAnnounce.complete();
233237
}
234238

235239
private _completeEnter() {
@@ -245,11 +249,8 @@ export class MatSnackBarContainer extends BasePortalOutlet implements OnDestroy,
245249
* removing an element which is in the middle of an animation.
246250
*/
247251
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);
251252
clearTimeout(this._exitFallback);
252-
this._ngZone.run(() => {
253+
queueMicrotask(() => {
253254
this._onExit.next();
254255
this._onExit.complete();
255256
});

tools/public_api_guard/material/snack-bar.md

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import { ComponentPortal } from '@angular/cdk/portal';
1212
import { ComponentRef } from '@angular/core';
1313
import { ComponentType } from '@angular/cdk/overlay';
1414
import { Direction } from '@angular/cdk/bidi';
15-
import { DoCheck } from '@angular/core';
1615
import { DomPortal } from '@angular/cdk/portal';
1716
import { ElementRef } from '@angular/core';
1817
import { EmbeddedViewRef } from '@angular/core';
@@ -94,11 +93,11 @@ export class MatSnackBarConfig<D = any> {
9493
}
9594

9695
// @public
97-
export class MatSnackBarContainer extends BasePortalOutlet implements OnDestroy, DoCheck {
96+
export class MatSnackBarContainer extends BasePortalOutlet implements OnDestroy {
9897
constructor(...args: unknown[]);
99-
protected _animationDone(event: AnimationEvent): void;
10098
// (undocumented)
10199
protected _animationsDisabled: boolean;
100+
_animationState: string;
102101
attachComponentPortal<T>(portal: ComponentPortal<T>): ComponentRef<T>;
103102
// @deprecated
104103
attachDomPortal: (portal: DomPortal) => void;
@@ -108,10 +107,8 @@ export class MatSnackBarContainer extends BasePortalOutlet implements OnDestroy,
108107
_label: ElementRef;
109108
_live: AriaLivePoliteness;
110109
readonly _liveElementId: string;
111-
// (undocumented)
112-
ngDoCheck(): void;
113-
// (undocumented)
114110
ngOnDestroy(): void;
111+
onAnimationEnd(event: AnimationEvent): void;
115112
readonly _onAnnounce: Subject<void>;
116113
readonly _onEnter: Subject<void>;
117114
readonly _onExit: Subject<void>;

0 commit comments

Comments
 (0)