Skip to content

Commit 2a2cd9c

Browse files
authored
fix(material/menu): item highlighted state not updating in time when using lazy content (#23185)
`matMenuContent` is declared as an `ng-template` and stamped out inside the menu which means that its change detection tree is actually in the declaration place, rather than the insertion place. This can result in the `highlighted` state not being updated when inside an `OnPush` component. Fixes #23175.
1 parent 4279ff1 commit 2a2cd9c

File tree

4 files changed

+26
-6
lines changed

4 files changed

+26
-6
lines changed

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
Inject,
1414
ElementRef,
1515
Optional,
16+
ChangeDetectorRef,
1617
} from '@angular/core';
1718
import {
1819
MAT_RIPPLE_GLOBAL_OPTIONS,
@@ -62,8 +63,9 @@ export class MatMenuItem extends BaseMatMenuItem {
6263
@Inject(MAT_MENU_PANEL) @Optional() parentMenu?: MatMenuPanel<unknown>,
6364
@Optional() @Inject(MAT_RIPPLE_GLOBAL_OPTIONS)
6465
globalRippleOptions?: RippleGlobalOptions,
65-
@Optional() @Inject(ANIMATION_MODULE_TYPE) animationMode?: string) {
66-
super(elementRef, document, focusMonitor, parentMenu);
66+
@Optional() @Inject(ANIMATION_MODULE_TYPE) animationMode?: string,
67+
changeDetectorRef?: ChangeDetectorRef) {
68+
super(elementRef, document, focusMonitor, parentMenu, changeDetectorRef);
6769

6870
this._noopAnimations = animationMode === 'NoopAnimations';
6971
this._rippleAnimation = globalRippleOptions?.animation || {

src/material/menu/menu-item.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
Input,
2020
HostListener,
2121
AfterViewInit,
22+
ChangeDetectorRef,
2223
} from '@angular/core';
2324
import {
2425
CanDisable,
@@ -81,7 +82,12 @@ export class MatMenuItem extends _MatMenuItemBase
8182
*/
8283
@Inject(DOCUMENT) _document?: any,
8384
private _focusMonitor?: FocusMonitor,
84-
@Inject(MAT_MENU_PANEL) @Optional() public _parentMenu?: MatMenuPanel<MatMenuItem>) {
85+
@Inject(MAT_MENU_PANEL) @Optional() public _parentMenu?: MatMenuPanel<MatMenuItem>,
86+
/**
87+
* @deprecated `_changeDetectorRef` to become a required parameter.
88+
* @breaking-change 14.0.0
89+
*/
90+
private _changeDetectorRef?: ChangeDetectorRef) {
8591

8692
// @breaking-change 8.0.0 make `_focusMonitor` and `document` required params.
8793
super();
@@ -173,6 +179,15 @@ export class MatMenuItem extends _MatMenuItemBase
173179
return clone.textContent?.trim() || '';
174180
}
175181

182+
_setHighlighted(isHighlighted: boolean) {
183+
// We need to mark this for check for the case where the content is coming from a
184+
// `matMenuContent` whose change detection tree is at the declaration position,
185+
// not the insertion position. See #23175.
186+
// @breaking-change 14.0.0 Remove null check for `_changeDetectorRef`.
187+
this._highlighted = isHighlighted;
188+
this._changeDetectorRef?.markForCheck();
189+
}
190+
176191
static ngAcceptInputType_disabled: BooleanInput;
177192
static ngAcceptInputType_disableRipple: BooleanInput;
178193
}

src/material/menu/menu-trigger.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -380,7 +380,7 @@ export class MatMenuTrigger implements AfterContentInit, OnDestroy {
380380
this._menuOpen ? this.menuOpened.emit() : this.menuClosed.emit();
381381

382382
if (this.triggersSubmenu()) {
383-
this._menuItemInstance._highlighted = isOpen;
383+
this._menuItemInstance._setHighlighted(isOpen);
384384
}
385385
}
386386

tools/public_api_guard/material/menu.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,8 @@ export class _MatMenuDirectivesModule {
198198
// @public
199199
export class MatMenuItem extends _MatMenuItemBase implements FocusableOption, CanDisable, CanDisableRipple, AfterViewInit, OnDestroy {
200200
constructor(_elementRef: ElementRef<HTMLElement>,
201-
_document?: any, _focusMonitor?: FocusMonitor | undefined, _parentMenu?: MatMenuPanel<MatMenuItem> | undefined);
201+
_document?: any, _focusMonitor?: FocusMonitor | undefined, _parentMenu?: MatMenuPanel<MatMenuItem> | undefined,
202+
_changeDetectorRef?: ChangeDetectorRef | undefined);
202203
_checkDisabled(event: Event): void;
203204
focus(origin?: FocusOrigin, options?: FocusOptions): void;
204205
readonly _focused: Subject<MatMenuItem>;
@@ -219,11 +220,13 @@ export class MatMenuItem extends _MatMenuItemBase implements FocusableOption, Ca
219220
// (undocumented)
220221
_parentMenu?: MatMenuPanel<MatMenuItem> | undefined;
221222
role: 'menuitem' | 'menuitemradio' | 'menuitemcheckbox';
223+
// (undocumented)
224+
_setHighlighted(isHighlighted: boolean): void;
222225
_triggersSubmenu: boolean;
223226
// (undocumented)
224227
static ɵcmp: i0.ɵɵComponentDeclaration<MatMenuItem, "[mat-menu-item]", ["matMenuItem"], { "disabled": "disabled"; "disableRipple": "disableRipple"; "role": "role"; }, {}, never, ["*"]>;
225228
// (undocumented)
226-
static ɵfac: i0.ɵɵFactoryDeclaration<MatMenuItem, [null, null, null, { optional: true; }]>;
229+
static ɵfac: i0.ɵɵFactoryDeclaration<MatMenuItem, [null, null, null, { optional: true; }, null]>;
227230
}
228231

229232
// @public (undocumented)

0 commit comments

Comments
 (0)