Skip to content

Commit

Permalink
fix(material/snack-bar): ensure that the snack bar always runs inside…
Browse files Browse the repository at this point in the history
… the NgZone (angular#24611)

Adds an extra call to ensure that the snack bar is inside the NgZone. This is something that has come up several times in internal tests where some API call is stubbed out, pulling the snack bar outside the zone and causing tests to fail when we remove a change detection somewhere.

**Note:** I had a hard time reproducing this in our own tests, presumably because the fixture ends up pulling it back into the zone.
  • Loading branch information
crisbeto authored and forsti0506 committed Apr 3, 2022
1 parent ef08dca commit b42ed80
Show file tree
Hide file tree
Showing 2 changed files with 35 additions and 23 deletions.
22 changes: 14 additions & 8 deletions src/material-experimental/mdc-snack-bar/snack-bar-container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,18 +176,24 @@ export class MatSnackBarContainer
enter() {
// MDC uses some browser APIs that will throw during server-side rendering.
if (this._platform.isBrowser) {
this._mdcFoundation.open();
this._screenReaderAnnounce();
this._ngZone.run(() => {
this._mdcFoundation.open();
this._screenReaderAnnounce();
});
}
}

exit(): Observable<void> {
this._exiting = true;
this._mdcFoundation.close();

// If the snack bar hasn't been announced by the time it exits it wouldn't have been open
// long enough to visually read it either, so clear the timeout for announcing.
clearTimeout(this._announceTimeoutId);
// It's common for snack bars to be opened by random outside calls like HTTP requests or
// errors. Run inside the NgZone to ensure that it functions correctly.
this._ngZone.run(() => {
this._exiting = true;
this._mdcFoundation.close();

// If the snack bar hasn't been announced by the time it exits it wouldn't have been open
// long enough to visually read it either, so clear the timeout for announcing.
clearTimeout(this._announceTimeoutId);
});

return this._onExit;
}
Expand Down
36 changes: 21 additions & 15 deletions src/material/snack-bar/snack-bar-container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,19 +194,23 @@ export class MatSnackBarContainer

/** Begin animation of the snack bar exiting from view. */
exit(): Observable<void> {
// Note: this one transitions to `hidden`, rather than `void`, in order to handle the case
// where multiple snack bars are opened in quick succession (e.g. two consecutive calls to
// `MatSnackBar.open`).
this._animationState = 'hidden';

// Mark this element with an 'exit' attribute to indicate that the snackbar has
// been dismissed and will soon be removed from the DOM. This is used by the snackbar
// test harness.
this._elementRef.nativeElement.setAttribute('mat-exit', '');

// If the snack bar hasn't been announced by the time it exits it wouldn't have been open
// long enough to visually read it either, so clear the timeout for announcing.
clearTimeout(this._announceTimeoutId);
// It's common for snack bars to be opened by random outside calls like HTTP requests or
// errors. Run inside the NgZone to ensure that it functions correctly.
this._ngZone.run(() => {
// Note: this one transitions to `hidden`, rather than `void`, in order to handle the case
// where multiple snack bars are opened in quick succession (e.g. two consecutive calls to
// `MatSnackBar.open`).
this._animationState = 'hidden';

// Mark this element with an 'exit' attribute to indicate that the snackbar has
// been dismissed and will soon be removed from the DOM. This is used by the snackbar
// test harness.
this._elementRef.nativeElement.setAttribute('mat-exit', '');

// If the snack bar hasn't been announced by the time it exits it wouldn't have been open
// long enough to visually read it either, so clear the timeout for announcing.
clearTimeout(this._announceTimeoutId);
});

return this._onExit;
}
Expand All @@ -223,8 +227,10 @@ export class MatSnackBarContainer
*/
private _completeExit() {
this._ngZone.onMicrotaskEmpty.pipe(take(1)).subscribe(() => {
this._onExit.next();
this._onExit.complete();
this._ngZone.run(() => {
this._onExit.next();
this._onExit.complete();
});
});
}

Expand Down

0 comments on commit b42ed80

Please sign in to comment.