I am using a matMenu in an Angular project which is populated dynamically from an array. This menu can have one level of sub-menu's. My menu definition array looks like this:
{
text: string,
subMenuName: string,
subMenuItems: [{
text: string,
onClick(): void
}]
}
I am trying to build this menu in HTML as follows:
<mat-menu #menu="matMenu">
<button *ngFor="let item of menuItems" [matMenuTriggerFor]="menuItem.subMenuName" mat-menu-item>
{{ item.text }}
</button>
</mat-menu>
<ng-container *ngFor="item of menuItems">
<mat-menu #item.subMenuName="matMenu">
<button *ngFor="let subItem of item.subMenuItems (click)="subItem.onClick();">
{{ subItem.text }}
</button>
</mat-menu>
</ng-container>
When I try to run this, it is not compliing and it is giving the following error:
ERROR TypeError: Cannot read property 'subscribe' of undefined
at MatMenuTrigger.push../node_modules/@angular/material/esm5/menu.es5.js.MatMenuTrigger.ngAfterContentInit
matMenuTriggerFor is passed the menu identifier to attach the menus.
Changing mat menu positionThe xPosition attribute sets the menu all along the horizontal axis. The yPosition attribute is used to change the menu's vertical position. Set the yPosition property to "above" to display the menu just above the menu trigger element. The values "above" and "below" are accepted by yPosition.
Angular Material provides the event to detect if the menu is open or not. menuOpened: Event emitted when the associated menu is opened. menuClosed: Event emitted when the associated menu is closed.
Solution was to create a component which referenced itself recursively. Code below:
TS
import { Component, Input, ViewChild } from '@angular/core';
import { NavItem } from './nav-item/nav-item';
@Component({
selector: 'app-menu-item',
templateUrl: './menu-item.component.html',
styleUrls: ['./menu-item.component.css']
})
export class MenuItemComponent {
@Input('items')
public items: NavItem[];
@ViewChild('childMenu')
public childMenu;
constructor() { }
}
HTML
<mat-menu #childMenu="matMenu" [overlapTrigger]="false">
<span *ngFor="let child of items">
<span *ngIf="child.children && child.children.length > 0">
<button mat-menu-item color="primary" [matMenuTriggerFor]="menu.childMenu">
<mat-icon>{{ child.iconName }}</mat-icon>
<span>{{ child.displayName }}</span>
</button>
<app-menu-item #menu [items]="child.children"></app-menu-item>
</span>
<span *ngIf="!child.children || child.children.length === 0">
<button mat-menu-item (click)="child.onClick();">
<mat-icon>{{ child.iconName }}</mat-icon>
<span>{{ child.displayName }}</span>
</button>
</span>
</span>
</mat-menu>
Where NavItem
is an interface:
export interface NavItem {
displayName: string;
iconName?: string;
children?: NavItem[];
onClick?(): void;
}
Then I simply need to reference <app-menu-item [items]="..">
in my HTML.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With