Skip to content

Commit 4e18181

Browse files
committed
fix(material/menu): position classes not update when window is resized
Relates to internal bug b/218602349. We were calling `MatMenu.setPositionClasses` in order to set the classes if the position changes, but the call was outside of the `NgZone` so nothing was happening. These changes bring the call back into the `NgZone`.
1 parent e04e4a7 commit 4e18181

File tree

4 files changed

+75
-2
lines changed

4 files changed

+75
-2
lines changed

src/material-experimental/mdc-menu/menu.spec.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1331,6 +1331,29 @@ describe('MDC-based MatMenu', () => {
13311331
expect(panel.classList).not.toContain('mat-menu-above');
13321332
}));
13331333

1334+
it('should update the position classes if the window is resized', fakeAsync(() => {
1335+
trigger.style.position = 'fixed';
1336+
trigger.style.top = '300px';
1337+
fixture.componentInstance.yPosition = 'above';
1338+
fixture.componentInstance.trigger.openMenu();
1339+
fixture.detectChanges();
1340+
tick(500);
1341+
1342+
const panel = overlayContainerElement.querySelector('.mat-mdc-menu-panel') as HTMLElement;
1343+
1344+
expect(panel.classList).toContain('mat-menu-above');
1345+
expect(panel.classList).not.toContain('mat-menu-below');
1346+
1347+
trigger.style.top = '0';
1348+
dispatchFakeEvent(window, 'resize');
1349+
fixture.detectChanges();
1350+
tick(500);
1351+
fixture.detectChanges();
1352+
1353+
expect(panel.classList).not.toContain('mat-menu-above');
1354+
expect(panel.classList).toContain('mat-menu-below');
1355+
}));
1356+
13341357
it('should be able to update the position after the first open', fakeAsync(() => {
13351358
trigger.style.position = 'fixed';
13361359
trigger.style.top = '200px';

src/material/menu/menu-trigger.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import {
3232
Inject,
3333
InjectionToken,
3434
Input,
35+
NgZone,
3536
OnDestroy,
3637
Optional,
3738
Output,
@@ -200,6 +201,22 @@ export abstract class _MatMenuTriggerBase implements AfterContentInit, OnDestroy
200201
focusMonitor?: FocusMonitor | null,
201202
);
202203

204+
/**
205+
* @deprecated `ngZone` will become a required parameter.
206+
* @breaking-change 15.0.0
207+
*/
208+
constructor(
209+
overlay: Overlay,
210+
element: ElementRef<HTMLElement>,
211+
viewContainerRef: ViewContainerRef,
212+
scrollStrategy: any,
213+
parentMenu: MatMenuPanel,
214+
menuItemInstance: MatMenuItem,
215+
dir: Directionality,
216+
focusMonitor: FocusMonitor,
217+
ngZone?: NgZone,
218+
);
219+
203220
constructor(
204221
private _overlay: Overlay,
205222
private _element: ElementRef<HTMLElement>,
@@ -211,6 +228,7 @@ export abstract class _MatMenuTriggerBase implements AfterContentInit, OnDestroy
211228
@Optional() @Self() private _menuItemInstance: MatMenuItem,
212229
@Optional() private _dir: Directionality,
213230
private _focusMonitor: FocusMonitor | null,
231+
private _ngZone?: NgZone,
214232
) {
215233
this._scrollStrategy = scrollStrategy;
216234
this._parentMaterialMenu = parentMenu instanceof _MatMenuBase ? parentMenu : undefined;
@@ -472,7 +490,14 @@ export abstract class _MatMenuTriggerBase implements AfterContentInit, OnDestroy
472490
const posX: MenuPositionX = change.connectionPair.overlayX === 'start' ? 'after' : 'before';
473491
const posY: MenuPositionY = change.connectionPair.overlayY === 'top' ? 'below' : 'above';
474492

475-
this.menu.setPositionClasses!(posX, posY);
493+
// @breaking-change 15.0.0 Remove null check for `ngZone`.
494+
// `positionChanges` fires outside of the `ngZone` and `setPositionClasses` might be
495+
// updating something in the view so we need to bring it back in.
496+
if (this._ngZone) {
497+
this._ngZone.run(() => this.menu.setPositionClasses!(posX, posY));
498+
} else {
499+
this.menu.setPositionClasses!(posX, posY);
500+
}
476501
});
477502
}
478503
}

src/material/menu/menu.spec.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1326,6 +1326,29 @@ describe('MatMenu', () => {
13261326
expect(panel.classList).not.toContain('mat-menu-above');
13271327
}));
13281328

1329+
it('should update the position classes if the window is resized', fakeAsync(() => {
1330+
trigger.style.position = 'fixed';
1331+
trigger.style.top = '300px';
1332+
fixture.componentInstance.yPosition = 'above';
1333+
fixture.componentInstance.trigger.openMenu();
1334+
fixture.detectChanges();
1335+
tick(500);
1336+
1337+
const panel = overlayContainerElement.querySelector('.mat-menu-panel') as HTMLElement;
1338+
1339+
expect(panel.classList).toContain('mat-menu-above');
1340+
expect(panel.classList).not.toContain('mat-menu-below');
1341+
1342+
trigger.style.top = '0';
1343+
dispatchFakeEvent(window, 'resize');
1344+
fixture.detectChanges();
1345+
tick(500);
1346+
fixture.detectChanges();
1347+
1348+
expect(panel.classList).not.toContain('mat-menu-above');
1349+
expect(panel.classList).toContain('mat-menu-below');
1350+
}));
1351+
13291352
it('should be able to update the position after the first open', fakeAsync(() => {
13301353
trigger.style.position = 'fixed';
13311354
trigger.style.top = '200px';

tools/public_api_guard/material/menu.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,8 @@ export class MatMenuTrigger extends _MatMenuTriggerBase {
282282
export abstract class _MatMenuTriggerBase implements AfterContentInit, OnDestroy {
283283
// @deprecated
284284
constructor(overlay: Overlay, element: ElementRef<HTMLElement>, viewContainerRef: ViewContainerRef, scrollStrategy: any, parentMenu: MatMenuPanel, menuItemInstance: MatMenuItem, dir: Directionality, focusMonitor?: FocusMonitor | null);
285+
// @deprecated
286+
constructor(overlay: Overlay, element: ElementRef<HTMLElement>, viewContainerRef: ViewContainerRef, scrollStrategy: any, parentMenu: MatMenuPanel, menuItemInstance: MatMenuItem, dir: Directionality, focusMonitor: FocusMonitor, ngZone?: NgZone);
285287
closeMenu(): void;
286288
// @deprecated (undocumented)
287289
get _deprecatedMatMenuTriggerFor(): MatMenuPanel;
@@ -315,7 +317,7 @@ export abstract class _MatMenuTriggerBase implements AfterContentInit, OnDestroy
315317
// (undocumented)
316318
static ɵdir: i0.ɵɵDirectiveDeclaration<_MatMenuTriggerBase, never, never, { "_deprecatedMatMenuTriggerFor": "mat-menu-trigger-for"; "menu": "matMenuTriggerFor"; "menuData": "matMenuTriggerData"; "restoreFocus": "matMenuTriggerRestoreFocus"; }, { "menuOpened": "menuOpened"; "onMenuOpen": "onMenuOpen"; "menuClosed": "menuClosed"; "onMenuClose": "onMenuClose"; }, never>;
317319
// (undocumented)
318-
static ɵfac: i0.ɵɵFactoryDeclaration<_MatMenuTriggerBase, [null, null, null, null, { optional: true; }, { optional: true; self: true; }, { optional: true; }, null]>;
320+
static ɵfac: i0.ɵɵFactoryDeclaration<_MatMenuTriggerBase, [null, null, null, null, { optional: true; }, { optional: true; self: true; }, { optional: true; }, null, null]>;
319321
}
320322

321323
// @public

0 commit comments

Comments
 (0)