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

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 committed Mar 17, 2022
1 parent 1199b39 commit 1acc15b
Show file tree
Hide file tree
Showing 3 changed files with 30 additions and 7 deletions.
5 changes: 3 additions & 2 deletions src/material-experimental/mdc-snack-bar/snack-bar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import {LiveAnnouncer} from '@angular/cdk/a11y';
import {BreakpointObserver} from '@angular/cdk/layout';
import {Overlay} from '@angular/cdk/overlay';
import {Inject, Injectable, Injector, Optional, SkipSelf} from '@angular/core';
import {Inject, Injectable, Injector, NgZone, Optional, SkipSelf} from '@angular/core';
import {
MatSnackBarConfig,
MAT_SNACK_BAR_DEFAULT_OPTIONS,
Expand All @@ -35,7 +35,8 @@ export class MatSnackBar extends _MatSnackBarBase {
breakpointObserver: BreakpointObserver,
@Optional() @SkipSelf() parentSnackBar: MatSnackBar,
@Inject(MAT_SNACK_BAR_DEFAULT_OPTIONS) defaultConfig: MatSnackBarConfig,
ngZone?: NgZone,
) {
super(overlay, live, injector, breakpointObserver, parentSnackBar, defaultConfig);
super(overlay, live, injector, breakpointObserver, parentSnackBar, defaultConfig, ngZone);
}
}
23 changes: 22 additions & 1 deletion src/material/snack-bar/snack-bar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
TemplateRef,
OnDestroy,
Type,
NgZone,
} from '@angular/core';
import {takeUntil} from 'rxjs/operators';
import {TextOnlySnackBar, SimpleSnackBar} from './simple-snack-bar';
Expand Down Expand Up @@ -83,6 +84,11 @@ export abstract class _MatSnackBarBase implements OnDestroy {
private _breakpointObserver: BreakpointObserver,
@Optional() @SkipSelf() private _parentSnackBar: _MatSnackBarBase,
@Inject(MAT_SNACK_BAR_DEFAULT_OPTIONS) private _defaultConfig: MatSnackBarConfig,
/**
* @deprecated `_ngZone` parameter to become required.
* @breaking-change 15.0.0
*/
private _ngZone?: NgZone,
) {}

/**
Expand All @@ -93,6 +99,13 @@ export abstract class _MatSnackBarBase implements OnDestroy {
* @param config Extra configuration for the snack bar.
*/
openFromComponent<T>(component: ComponentType<T>, config?: MatSnackBarConfig): MatSnackBarRef<T> {
// @breaking-change 15.0.0 Remove null check for _ngZone.
if (this._ngZone) {
// 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.
return this._ngZone.run(() => this._attach(component, config) as MatSnackBarRef<T>);
}

return this._attach(component, config) as MatSnackBarRef<T>;
}

Expand All @@ -107,6 +120,13 @@ export abstract class _MatSnackBarBase implements OnDestroy {
template: TemplateRef<any>,
config?: MatSnackBarConfig,
): MatSnackBarRef<EmbeddedViewRef<any>> {
// @breaking-change 15.0.0 Remove null check for _ngZone.
if (this._ngZone) {
// 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.
return this._ngZone.run(() => this._attach(template, config));
}

return this._attach(template, config);
}

Expand Down Expand Up @@ -325,7 +345,8 @@ export class MatSnackBar extends _MatSnackBarBase {
breakpointObserver: BreakpointObserver,
@Optional() @SkipSelf() parentSnackBar: MatSnackBar,
@Inject(MAT_SNACK_BAR_DEFAULT_OPTIONS) defaultConfig: MatSnackBarConfig,
ngZone?: NgZone,
) {
super(overlay, live, injector, breakpointObserver, parentSnackBar, defaultConfig);
super(overlay, live, injector, breakpointObserver, parentSnackBar, defaultConfig, ngZone);
}
}
9 changes: 5 additions & 4 deletions tools/public_api_guard/material/snack-bar.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,15 @@ export function MAT_SNACK_BAR_DEFAULT_OPTIONS_FACTORY(): MatSnackBarConfig;

// @public
export class MatSnackBar extends _MatSnackBarBase {
constructor(overlay: Overlay, live: LiveAnnouncer, injector: Injector, breakpointObserver: BreakpointObserver, parentSnackBar: MatSnackBar, defaultConfig: MatSnackBarConfig);
constructor(overlay: Overlay, live: LiveAnnouncer, injector: Injector, breakpointObserver: BreakpointObserver, parentSnackBar: MatSnackBar, defaultConfig: MatSnackBarConfig, ngZone?: NgZone);
// (undocumented)
protected handsetCssClass: string;
// (undocumented)
protected simpleSnackBarComponent: typeof SimpleSnackBar;
// (undocumented)
protected snackBarContainerComponent: typeof MatSnackBarContainer;
// (undocumented)
static ɵfac: i0.ɵɵFactoryDeclaration<MatSnackBar, [null, null, null, null, { optional: true; skipSelf: true; }, null]>;
static ɵfac: i0.ɵɵFactoryDeclaration<MatSnackBar, [null, null, null, null, { optional: true; skipSelf: true; }, null, null]>;
// (undocumented)
static ɵprov: i0.ɵɵInjectableDeclaration<MatSnackBar>;
}
Expand All @@ -70,7 +70,8 @@ export const matSnackBarAnimations: {

// @public (undocumented)
export abstract class _MatSnackBarBase implements OnDestroy {
constructor(_overlay: Overlay, _live: LiveAnnouncer, _injector: Injector, _breakpointObserver: BreakpointObserver, _parentSnackBar: _MatSnackBarBase, _defaultConfig: MatSnackBarConfig);
constructor(_overlay: Overlay, _live: LiveAnnouncer, _injector: Injector, _breakpointObserver: BreakpointObserver, _parentSnackBar: _MatSnackBarBase, _defaultConfig: MatSnackBarConfig,
_ngZone?: NgZone | undefined);
dismiss(): void;
protected abstract handsetCssClass: string;
// (undocumented)
Expand All @@ -83,7 +84,7 @@ export abstract class _MatSnackBarBase implements OnDestroy {
protected abstract simpleSnackBarComponent: Type<TextOnlySnackBar>;
protected abstract snackBarContainerComponent: Type<_SnackBarContainer>;
// (undocumented)
static ɵfac: i0.ɵɵFactoryDeclaration<_MatSnackBarBase, [null, null, null, null, { optional: true; skipSelf: true; }, null]>;
static ɵfac: i0.ɵɵFactoryDeclaration<_MatSnackBarBase, [null, null, null, null, { optional: true; skipSelf: true; }, null, null]>;
// (undocumented)
static ɵprov: i0.ɵɵInjectableDeclaration<_MatSnackBarBase>;
}
Expand Down

0 comments on commit 1acc15b

Please sign in to comment.