Skip to content

Commit 77f0a77

Browse files
authored
fix(material/menu): aria-expanded not updating in an OnPush component (#26279)
Fixes that the value for `aria-expanded` wasn't being updated when the menu is closed inside an OnPush parent. Fixes #26262.
1 parent 836b777 commit 77f0a77

File tree

2 files changed

+62
-32
lines changed

2 files changed

+62
-32
lines changed

src/material/menu/menu-trigger.ts

+11-4
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,11 @@ import {
2626
import {TemplatePortal} from '@angular/cdk/portal';
2727
import {
2828
AfterContentInit,
29+
ChangeDetectorRef,
2930
Directive,
3031
ElementRef,
3132
EventEmitter,
33+
inject,
3234
Inject,
3335
InjectionToken,
3436
Input,
@@ -93,6 +95,7 @@ export abstract class _MatMenuTriggerBase implements AfterContentInit, OnDestroy
9395
private _hoverSubscription = Subscription.EMPTY;
9496
private _menuCloseSubscription = Subscription.EMPTY;
9597
private _scrollStrategy: () => ScrollStrategy;
98+
private _changeDetectorRef = inject(ChangeDetectorRef);
9699

97100
/**
98101
* We're specifically looking for a `MatMenu` here since the generic `MatMenuPanel`
@@ -436,11 +439,15 @@ export abstract class _MatMenuTriggerBase implements AfterContentInit, OnDestroy
436439

437440
// set state rather than toggle to support triggers sharing a menu
438441
private _setIsMenuOpen(isOpen: boolean): void {
439-
this._menuOpen = isOpen;
440-
this._menuOpen ? this.menuOpened.emit() : this.menuClosed.emit();
442+
if (isOpen !== this._menuOpen) {
443+
this._menuOpen = isOpen;
444+
this._menuOpen ? this.menuOpened.emit() : this.menuClosed.emit();
441445

442-
if (this.triggersSubmenu()) {
443-
this._menuItemInstance._setHighlighted(isOpen);
446+
if (this.triggersSubmenu()) {
447+
this._menuItemInstance._setHighlighted(isOpen);
448+
}
449+
450+
this._changeDetectorRef.markForCheck();
444451
}
445452
}
446453

src/material/menu/menu.spec.ts

+51-28
Original file line numberDiff line numberDiff line change
@@ -897,6 +897,26 @@ describe('MDC-based MatMenu', () => {
897897
expect(triggerEl.getAttribute('aria-expanded')).toBe('false');
898898
}));
899899

900+
it('should toggle aria-expanded on the trigger in an OnPush component', fakeAsync(() => {
901+
const fixture = createComponent(SimpleMenuOnPush, [], [FakeIcon]);
902+
fixture.detectChanges();
903+
const triggerEl = fixture.componentInstance.triggerEl.nativeElement;
904+
905+
expect(triggerEl.getAttribute('aria-expanded')).toBe('false');
906+
907+
fixture.componentInstance.trigger.openMenu();
908+
fixture.detectChanges();
909+
tick(500);
910+
911+
expect(triggerEl.getAttribute('aria-expanded')).toBe('true');
912+
913+
fixture.componentInstance.trigger.closeMenu();
914+
fixture.detectChanges();
915+
tick(500);
916+
917+
expect(triggerEl.getAttribute('aria-expanded')).toBe('false');
918+
}));
919+
900920
it('should throw if assigning a menu that contains the trigger', fakeAsync(() => {
901921
expect(() => {
902922
const fixture = createComponent(InvalidRecursiveMenu, [], [FakeIcon]);
@@ -2737,34 +2757,34 @@ describe('MatMenu default overrides', () => {
27372757
}));
27382758
});
27392759

2740-
@Component({
2741-
template: `
2742-
<button
2743-
[matMenuTriggerFor]="menu"
2744-
[matMenuTriggerRestoreFocus]="restoreFocus"
2745-
#triggerEl>Toggle menu</button>
2746-
<mat-menu
2747-
#menu="matMenu"
2748-
[class]="panelClass"
2749-
(closed)="closeCallback($event)"
2750-
[backdropClass]="backdropClass"
2751-
[aria-label]="ariaLabel"
2752-
[aria-labelledby]="ariaLabelledby"
2753-
[aria-describedby]="ariaDescribedby">
2754-
2755-
<button mat-menu-item> Item </button>
2756-
<button mat-menu-item disabled> Disabled </button>
2757-
<button mat-menu-item disableRipple>
2758-
<mat-icon>unicorn</mat-icon>
2759-
Item with an icon
2760-
</button>
2761-
<button mat-menu-item>
2762-
<span>Item with text inside span</span>
2763-
</button>
2764-
<button *ngFor="let item of extraItems" mat-menu-item> {{item}} </button>
2765-
</mat-menu>
2766-
`,
2767-
})
2760+
const SIMPLE_MENU_TEMPLATE = `
2761+
<button
2762+
[matMenuTriggerFor]="menu"
2763+
[matMenuTriggerRestoreFocus]="restoreFocus"
2764+
#triggerEl>Toggle menu</button>
2765+
<mat-menu
2766+
#menu="matMenu"
2767+
[class]="panelClass"
2768+
(closed)="closeCallback($event)"
2769+
[backdropClass]="backdropClass"
2770+
[aria-label]="ariaLabel"
2771+
[aria-labelledby]="ariaLabelledby"
2772+
[aria-describedby]="ariaDescribedby">
2773+
2774+
<button mat-menu-item> Item </button>
2775+
<button mat-menu-item disabled> Disabled </button>
2776+
<button mat-menu-item disableRipple>
2777+
<mat-icon>unicorn</mat-icon>
2778+
Item with an icon
2779+
</button>
2780+
<button mat-menu-item>
2781+
<span>Item with text inside span</span>
2782+
</button>
2783+
<button *ngFor="let item of extraItems" mat-menu-item> {{item}} </button>
2784+
</mat-menu>
2785+
`;
2786+
2787+
@Component({template: SIMPLE_MENU_TEMPLATE})
27682788
class SimpleMenu {
27692789
@ViewChild(MatMenuTrigger) trigger: MatMenuTrigger;
27702790
@ViewChild('triggerEl') triggerEl: ElementRef<HTMLElement>;
@@ -2780,6 +2800,9 @@ class SimpleMenu {
27802800
ariaDescribedby: string;
27812801
}
27822802

2803+
@Component({template: SIMPLE_MENU_TEMPLATE, changeDetection: ChangeDetectionStrategy.OnPush})
2804+
class SimpleMenuOnPush extends SimpleMenu {}
2805+
27832806
@Component({
27842807
template: `
27852808
<button [matMenuTriggerFor]="menu" #triggerEl>Toggle menu</button>

0 commit comments

Comments
 (0)