From 6ef3cd45dd1c78ef14bf6f3b04da031858105aa0 Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Sun, 27 Mar 2022 09:16:26 +0200 Subject: [PATCH] fix(material/snack-bar): ensure that the snack bar always runs inside the NgZone 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. --- .../mdc-snack-bar/snack-bar-container.ts | 22 +++++++----- src/material/snack-bar/snack-bar-container.ts | 36 +++++++++++-------- 2 files changed, 35 insertions(+), 23 deletions(-) diff --git a/src/material-experimental/mdc-snack-bar/snack-bar-container.ts b/src/material-experimental/mdc-snack-bar/snack-bar-container.ts index a8a157561a63..afdc9fc72a77 100644 --- a/src/material-experimental/mdc-snack-bar/snack-bar-container.ts +++ b/src/material-experimental/mdc-snack-bar/snack-bar-container.ts @@ -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 { - 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; } diff --git a/src/material/snack-bar/snack-bar-container.ts b/src/material/snack-bar/snack-bar-container.ts index 2bf4a2d901d5..f405dd61ddd4 100644 --- a/src/material/snack-bar/snack-bar-container.ts +++ b/src/material/snack-bar/snack-bar-container.ts @@ -194,19 +194,23 @@ export class MatSnackBarContainer /** Begin animation of the snack bar exiting from view. */ exit(): Observable { - // 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; } @@ -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(); + }); }); }