From 6a3230e457b9841a6e06cbf39e2322b964c35541 Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Tue, 27 Jul 2021 06:42:16 +0200 Subject: [PATCH] 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. (cherry picked from commit 2a2cd9c3e4f96b1cf3e370cec2a485bc53a7e185) --- src/material-experimental/mdc-menu/menu-item.ts | 6 ++++-- src/material/menu/menu-item.ts | 17 ++++++++++++++++- src/material/menu/menu-trigger.ts | 2 +- tools/public_api_guard/material/menu.md | 7 +++++-- 4 files changed, 26 insertions(+), 6 deletions(-) diff --git a/src/material-experimental/mdc-menu/menu-item.ts b/src/material-experimental/mdc-menu/menu-item.ts index a39c657a400f..9e922e9b72d9 100644 --- a/src/material-experimental/mdc-menu/menu-item.ts +++ b/src/material-experimental/mdc-menu/menu-item.ts @@ -13,6 +13,7 @@ import { Inject, ElementRef, Optional, + ChangeDetectorRef, } from '@angular/core'; import { MAT_RIPPLE_GLOBAL_OPTIONS, @@ -62,8 +63,9 @@ export class MatMenuItem extends BaseMatMenuItem { @Inject(MAT_MENU_PANEL) @Optional() parentMenu?: MatMenuPanel, @Optional() @Inject(MAT_RIPPLE_GLOBAL_OPTIONS) globalRippleOptions?: RippleGlobalOptions, - @Optional() @Inject(ANIMATION_MODULE_TYPE) animationMode?: string) { - super(elementRef, document, focusMonitor, parentMenu); + @Optional() @Inject(ANIMATION_MODULE_TYPE) animationMode?: string, + changeDetectorRef?: ChangeDetectorRef) { + super(elementRef, document, focusMonitor, parentMenu, changeDetectorRef); this._noopAnimations = animationMode === 'NoopAnimations'; this._rippleAnimation = globalRippleOptions?.animation || { diff --git a/src/material/menu/menu-item.ts b/src/material/menu/menu-item.ts index b95e9c746bd0..b21b7a04af43 100644 --- a/src/material/menu/menu-item.ts +++ b/src/material/menu/menu-item.ts @@ -19,6 +19,7 @@ import { Input, HostListener, AfterViewInit, + ChangeDetectorRef, } from '@angular/core'; import { CanDisable, @@ -81,7 +82,12 @@ export class MatMenuItem extends _MatMenuItemBase */ @Inject(DOCUMENT) _document?: any, private _focusMonitor?: FocusMonitor, - @Inject(MAT_MENU_PANEL) @Optional() public _parentMenu?: MatMenuPanel) { + @Inject(MAT_MENU_PANEL) @Optional() public _parentMenu?: MatMenuPanel, + /** + * @deprecated `_changeDetectorRef` to become a required parameter. + * @breaking-change 14.0.0 + */ + private _changeDetectorRef?: ChangeDetectorRef) { // @breaking-change 8.0.0 make `_focusMonitor` and `document` required params. super(); @@ -173,6 +179,15 @@ export class MatMenuItem extends _MatMenuItemBase return clone.textContent?.trim() || ''; } + _setHighlighted(isHighlighted: boolean) { + // We need to mark this for check for the case where the content is coming from a + // `matMenuContent` whose change detection tree is at the declaration position, + // not the insertion position. See #23175. + // @breaking-change 14.0.0 Remove null check for `_changeDetectorRef`. + this._highlighted = isHighlighted; + this._changeDetectorRef?.markForCheck(); + } + static ngAcceptInputType_disabled: BooleanInput; static ngAcceptInputType_disableRipple: BooleanInput; } diff --git a/src/material/menu/menu-trigger.ts b/src/material/menu/menu-trigger.ts index 96d157ec256d..0c121d41c9f2 100644 --- a/src/material/menu/menu-trigger.ts +++ b/src/material/menu/menu-trigger.ts @@ -380,7 +380,7 @@ export class MatMenuTrigger implements AfterContentInit, OnDestroy { this._menuOpen ? this.menuOpened.emit() : this.menuClosed.emit(); if (this.triggersSubmenu()) { - this._menuItemInstance._highlighted = isOpen; + this._menuItemInstance._setHighlighted(isOpen); } } diff --git a/tools/public_api_guard/material/menu.md b/tools/public_api_guard/material/menu.md index 8a066b093f1a..2d4f2da73a9d 100644 --- a/tools/public_api_guard/material/menu.md +++ b/tools/public_api_guard/material/menu.md @@ -198,7 +198,8 @@ export class _MatMenuDirectivesModule { // @public export class MatMenuItem extends _MatMenuItemBase implements FocusableOption, CanDisable, CanDisableRipple, AfterViewInit, OnDestroy { constructor(_elementRef: ElementRef, - _document?: any, _focusMonitor?: FocusMonitor | undefined, _parentMenu?: MatMenuPanel | undefined); + _document?: any, _focusMonitor?: FocusMonitor | undefined, _parentMenu?: MatMenuPanel | undefined, + _changeDetectorRef?: ChangeDetectorRef | undefined); _checkDisabled(event: Event): void; focus(origin?: FocusOrigin, options?: FocusOptions): void; readonly _focused: Subject; @@ -219,11 +220,13 @@ export class MatMenuItem extends _MatMenuItemBase implements FocusableOption, Ca // (undocumented) _parentMenu?: MatMenuPanel | undefined; role: 'menuitem' | 'menuitemradio' | 'menuitemcheckbox'; + // (undocumented) + _setHighlighted(isHighlighted: boolean): void; _triggersSubmenu: boolean; // (undocumented) static ɵcmp: i0.ɵɵComponentDeclaration; // (undocumented) - static ɵfac: i0.ɵɵFactoryDeclaration; + static ɵfac: i0.ɵɵFactoryDeclaration; } // @public (undocumented)