Skip to content

Commit 1acc15b

Browse files
committed
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.
1 parent 1199b39 commit 1acc15b

File tree

3 files changed

+30
-7
lines changed

3 files changed

+30
-7
lines changed

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import {LiveAnnouncer} from '@angular/cdk/a11y';
1010
import {BreakpointObserver} from '@angular/cdk/layout';
1111
import {Overlay} from '@angular/cdk/overlay';
12-
import {Inject, Injectable, Injector, Optional, SkipSelf} from '@angular/core';
12+
import {Inject, Injectable, Injector, NgZone, Optional, SkipSelf} from '@angular/core';
1313
import {
1414
MatSnackBarConfig,
1515
MAT_SNACK_BAR_DEFAULT_OPTIONS,
@@ -35,7 +35,8 @@ export class MatSnackBar extends _MatSnackBarBase {
3535
breakpointObserver: BreakpointObserver,
3636
@Optional() @SkipSelf() parentSnackBar: MatSnackBar,
3737
@Inject(MAT_SNACK_BAR_DEFAULT_OPTIONS) defaultConfig: MatSnackBarConfig,
38+
ngZone?: NgZone,
3839
) {
39-
super(overlay, live, injector, breakpointObserver, parentSnackBar, defaultConfig);
40+
super(overlay, live, injector, breakpointObserver, parentSnackBar, defaultConfig, ngZone);
4041
}
4142
}

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

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
TemplateRef,
2323
OnDestroy,
2424
Type,
25+
NgZone,
2526
} from '@angular/core';
2627
import {takeUntil} from 'rxjs/operators';
2728
import {TextOnlySnackBar, SimpleSnackBar} from './simple-snack-bar';
@@ -83,6 +84,11 @@ export abstract class _MatSnackBarBase implements OnDestroy {
8384
private _breakpointObserver: BreakpointObserver,
8485
@Optional() @SkipSelf() private _parentSnackBar: _MatSnackBarBase,
8586
@Inject(MAT_SNACK_BAR_DEFAULT_OPTIONS) private _defaultConfig: MatSnackBarConfig,
87+
/**
88+
* @deprecated `_ngZone` parameter to become required.
89+
* @breaking-change 15.0.0
90+
*/
91+
private _ngZone?: NgZone,
8692
) {}
8793

8894
/**
@@ -93,6 +99,13 @@ export abstract class _MatSnackBarBase implements OnDestroy {
9399
* @param config Extra configuration for the snack bar.
94100
*/
95101
openFromComponent<T>(component: ComponentType<T>, config?: MatSnackBarConfig): MatSnackBarRef<T> {
102+
// @breaking-change 15.0.0 Remove null check for _ngZone.
103+
if (this._ngZone) {
104+
// It's common for snack bars to be opened by random outside calls like HTTP requests or
105+
// errors. Run inside the NgZone to ensure that it functions correctly.
106+
return this._ngZone.run(() => this._attach(component, config) as MatSnackBarRef<T>);
107+
}
108+
96109
return this._attach(component, config) as MatSnackBarRef<T>;
97110
}
98111

@@ -107,6 +120,13 @@ export abstract class _MatSnackBarBase implements OnDestroy {
107120
template: TemplateRef<any>,
108121
config?: MatSnackBarConfig,
109122
): MatSnackBarRef<EmbeddedViewRef<any>> {
123+
// @breaking-change 15.0.0 Remove null check for _ngZone.
124+
if (this._ngZone) {
125+
// It's common for snack bars to be opened by random outside calls like HTTP requests or
126+
// errors. Run inside the NgZone to ensure that it functions correctly.
127+
return this._ngZone.run(() => this._attach(template, config));
128+
}
129+
110130
return this._attach(template, config);
111131
}
112132

@@ -325,7 +345,8 @@ export class MatSnackBar extends _MatSnackBarBase {
325345
breakpointObserver: BreakpointObserver,
326346
@Optional() @SkipSelf() parentSnackBar: MatSnackBar,
327347
@Inject(MAT_SNACK_BAR_DEFAULT_OPTIONS) defaultConfig: MatSnackBarConfig,
348+
ngZone?: NgZone,
328349
) {
329-
super(overlay, live, injector, breakpointObserver, parentSnackBar, defaultConfig);
350+
super(overlay, live, injector, breakpointObserver, parentSnackBar, defaultConfig, ngZone);
330351
}
331352
}

tools/public_api_guard/material/snack-bar.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,15 +50,15 @@ export function MAT_SNACK_BAR_DEFAULT_OPTIONS_FACTORY(): MatSnackBarConfig;
5050

5151
// @public
5252
export class MatSnackBar extends _MatSnackBarBase {
53-
constructor(overlay: Overlay, live: LiveAnnouncer, injector: Injector, breakpointObserver: BreakpointObserver, parentSnackBar: MatSnackBar, defaultConfig: MatSnackBarConfig);
53+
constructor(overlay: Overlay, live: LiveAnnouncer, injector: Injector, breakpointObserver: BreakpointObserver, parentSnackBar: MatSnackBar, defaultConfig: MatSnackBarConfig, ngZone?: NgZone);
5454
// (undocumented)
5555
protected handsetCssClass: string;
5656
// (undocumented)
5757
protected simpleSnackBarComponent: typeof SimpleSnackBar;
5858
// (undocumented)
5959
protected snackBarContainerComponent: typeof MatSnackBarContainer;
6060
// (undocumented)
61-
static ɵfac: i0.ɵɵFactoryDeclaration<MatSnackBar, [null, null, null, null, { optional: true; skipSelf: true; }, null]>;
61+
static ɵfac: i0.ɵɵFactoryDeclaration<MatSnackBar, [null, null, null, null, { optional: true; skipSelf: true; }, null, null]>;
6262
// (undocumented)
6363
static ɵprov: i0.ɵɵInjectableDeclaration<MatSnackBar>;
6464
}
@@ -70,7 +70,8 @@ export const matSnackBarAnimations: {
7070

7171
// @public (undocumented)
7272
export abstract class _MatSnackBarBase implements OnDestroy {
73-
constructor(_overlay: Overlay, _live: LiveAnnouncer, _injector: Injector, _breakpointObserver: BreakpointObserver, _parentSnackBar: _MatSnackBarBase, _defaultConfig: MatSnackBarConfig);
73+
constructor(_overlay: Overlay, _live: LiveAnnouncer, _injector: Injector, _breakpointObserver: BreakpointObserver, _parentSnackBar: _MatSnackBarBase, _defaultConfig: MatSnackBarConfig,
74+
_ngZone?: NgZone | undefined);
7475
dismiss(): void;
7576
protected abstract handsetCssClass: string;
7677
// (undocumented)
@@ -83,7 +84,7 @@ export abstract class _MatSnackBarBase implements OnDestroy {
8384
protected abstract simpleSnackBarComponent: Type<TextOnlySnackBar>;
8485
protected abstract snackBarContainerComponent: Type<_SnackBarContainer>;
8586
// (undocumented)
86-
static ɵfac: i0.ɵɵFactoryDeclaration<_MatSnackBarBase, [null, null, null, null, { optional: true; skipSelf: true; }, null]>;
87+
static ɵfac: i0.ɵɵFactoryDeclaration<_MatSnackBarBase, [null, null, null, null, { optional: true; skipSelf: true; }, null, null]>;
8788
// (undocumented)
8889
static ɵprov: i0.ɵɵInjectableDeclaration<_MatSnackBarBase>;
8990
}

0 commit comments

Comments
 (0)