Skip to content

Commit 399ca44

Browse files
committed
fixup! fix(material/snack-bar): switch away from animations module
1 parent 06bff69 commit 399ca44

File tree

3 files changed

+49
-30
lines changed

3 files changed

+49
-30
lines changed

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

Lines changed: 39 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@
99
import {
1010
ANIMATION_MODULE_TYPE,
1111
ChangeDetectionStrategy,
12+
ChangeDetectorRef,
1213
Component,
1314
ComponentRef,
15+
DoCheck,
1416
ElementRef,
1517
EmbeddedViewRef,
1618
inject,
@@ -53,16 +55,18 @@ import {MatSnackBarConfig} from './snack-bar-config';
5355
'(animationend)': '_animationDone($event)',
5456
},
5557
})
56-
export class MatSnackBarContainer extends BasePortalOutlet implements OnDestroy {
58+
export class MatSnackBarContainer extends BasePortalOutlet implements OnDestroy, DoCheck {
5759
private _ngZone = inject(NgZone);
5860
private _elementRef = inject<ElementRef<HTMLElement>>(ElementRef);
5961
private _platform = inject(Platform);
62+
private _changeDetectorRef = inject(ChangeDetectorRef);
6063
private _enterFallback: ReturnType<typeof setTimeout> | undefined;
6164
private _exitFallback: ReturnType<typeof setTimeout> | undefined;
6265
protected _animationsDisabled =
6366
inject(ANIMATION_MODULE_TYPE, {optional: true}) === 'NoopAnimations';
64-
snackBarConfig = inject(MatSnackBarConfig);
67+
private _scheduleDelayedEnter: boolean;
6568

69+
snackBarConfig = inject(MatSnackBarConfig);
6670
private _document = inject(DOCUMENT);
6771
private _trackedModals = new Set<Element>();
6872

@@ -174,15 +178,23 @@ export class MatSnackBarContainer extends BasePortalOutlet implements OnDestroy
174178
/** Begin animation of snack bar entrance into view. */
175179
enter(): void {
176180
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.
184+
this._changeDetectorRef.markForCheck();
185+
this._changeDetectorRef.detectChanges();
177186
this._screenReaderAnnounce();
178187

179188
if (this._animationsDisabled) {
180-
this._completeEnter();
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;
181193
} else {
182194
// Guarantees that the animation-related events will
183195
// fire even if something interrupts the animation.
184196
clearTimeout(this._enterFallback);
185-
this._enterFallback = setTimeout(this._completeEnter, 200);
197+
this._enterFallback = setTimeout(() => this._completeEnter(), 200);
186198
}
187199
}
188200
}
@@ -194,51 +206,54 @@ export class MatSnackBarContainer extends BasePortalOutlet implements OnDestroy
194206
// test harness.
195207
this._elementRef.nativeElement.setAttribute('mat-exit', '');
196208

197-
// If the snack bar hasn't been announced by the time it exits it wouldn't have been open
198-
// long enough to visually read it either, so clear the timeout for announcing.
199-
clearTimeout(this._announceTimeoutId);
200-
201-
if (this._animationsDisabled) {
202-
// It's common for snack bars to be opened by random outside calls like HTTP requests or
203-
// errors. Run inside the NgZone to ensure that it functions correctly.
204-
this._ngZone.run(this._completeExit);
205-
} else {
206-
// Guarantees that the animation-related events will
207-
// fire even if something interrupts the animation.
208-
clearTimeout(this._exitFallback);
209-
this._exitFallback = setTimeout(this._completeExit, 150);
210-
}
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+
);
211216

212217
return this._onExit;
213218
}
214219

215-
/** Makes sure the exit callbacks have been invoked when the element is destroyed. */
220+
ngDoCheck(): void {
221+
if (this._scheduleDelayedEnter) {
222+
this._scheduleDelayedEnter = false;
223+
this._completeEnter();
224+
}
225+
}
226+
216227
ngOnDestroy() {
217228
clearTimeout(this._enterFallback);
218229
this._destroyed = true;
219230
this._clearFromModals();
220231
this._completeExit();
232+
this._onAnnounce.complete();
221233
}
222234

223-
private _completeEnter = () => {
235+
private _completeEnter() {
224236
clearTimeout(this._enterFallback);
225237
this._ngZone.run(() => {
226238
this._onEnter.next();
227239
this._onEnter.complete();
228240
});
229-
};
241+
}
230242

231243
/**
232244
* Removes the element in a microtask. Helps prevent errors where we end up
233245
* removing an element which is in the middle of an animation.
234246
*/
235-
private _completeExit = () => {
247+
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);
236251
clearTimeout(this._exitFallback);
237-
queueMicrotask(() => {
252+
this._ngZone.run(() => {
238253
this._onExit.next();
239254
this._onExit.complete();
240255
});
241-
};
256+
}
242257

243258
/**
244259
* Called after the portal contents have been attached. Can be

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -242,11 +242,6 @@ export class MatSnackBar implements OnDestroy {
242242
}
243243
});
244244

245-
// If a dismiss timeout is provided, set up dismiss based on after the snackbar is opened.
246-
if (config.duration && config.duration > 0) {
247-
snackBarRef.afterOpened().subscribe(() => snackBarRef._dismissAfter(config.duration!));
248-
}
249-
250245
if (this._openedSnackBarRef) {
251246
// If a snack bar is already in view, dismiss it and enter the
252247
// new snack bar after exit animation is complete.
@@ -258,6 +253,11 @@ export class MatSnackBar implements OnDestroy {
258253
// If no snack bar is in view, enter the new snack bar.
259254
snackBarRef.containerInstance.enter();
260255
}
256+
257+
// If a dismiss timeout is provided, set up dismiss based on after the snackbar is opened.
258+
if (config.duration && config.duration > 0) {
259+
snackBarRef.afterOpened().subscribe(() => snackBarRef._dismissAfter(config.duration!));
260+
}
261261
}
262262

263263
/**

tools/public_api_guard/material/snack-bar.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ 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';
1516
import { DomPortal } from '@angular/cdk/portal';
1617
import { ElementRef } from '@angular/core';
1718
import { EmbeddedViewRef } from '@angular/core';
@@ -93,7 +94,7 @@ export class MatSnackBarConfig<D = any> {
9394
}
9495

9596
// @public
96-
export class MatSnackBarContainer extends BasePortalOutlet implements OnDestroy {
97+
export class MatSnackBarContainer extends BasePortalOutlet implements OnDestroy, DoCheck {
9798
constructor(...args: unknown[]);
9899
protected _animationDone(event: AnimationEvent): void;
99100
// (undocumented)
@@ -107,6 +108,9 @@ export class MatSnackBarContainer extends BasePortalOutlet implements OnDestroy
107108
_label: ElementRef;
108109
_live: AriaLivePoliteness;
109110
readonly _liveElementId: string;
111+
// (undocumented)
112+
ngDoCheck(): void;
113+
// (undocumented)
110114
ngOnDestroy(): void;
111115
readonly _onAnnounce: Subject<void>;
112116
readonly _onEnter: Subject<void>;

0 commit comments

Comments
 (0)