Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular matMenuTriggerFor programmatically

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
like image 403
William Moore Avatar asked May 17 '18 14:05

William Moore


People also ask

What is matMenuTriggerFor?

matMenuTriggerFor is passed the menu identifier to attach the menus.

How to change the position of mat menu?

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.

How can I check if a mat menu in material angular is open?

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.


1 Answers

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.

like image 51
William Moore Avatar answered Oct 08 '22 21:10

William Moore