Feature Request - Menu Docs
The docs would include an example of building a menu from a data structure similar to the radio button docs.
The menu docs only include a nested menu example using static HTML.
Working on a Stackblitz demo atm...
This is a common use case for building application menus, especially for business applications. The data may come from a config file, a REST API, or some other source but it is very often not static and does not align with a static HTML example.
Angular 4.4.3
TypeScript 2.3.4
Chrome 61
OS X El Capitan
Thank you for your hard work on this library!
https://github.com/angular/material2/issues/6765 was likely to block this use case, but the fix has been merged into master.
There is an example of creating md-menu's dynamically here but it doesn't demonstrate nesting.
I've finally got something working on the latest snapshots. It's not possible in beta.11 due to the bugs related to ngIf/ngFor and menus. I'm working on distilling it down to a simple docs-ready example atm.
@Splaktar I'm interested to see what you find...
I just filed #7542 because it's not currently possible to use a recursive ng-template for creating a nested menu.
hey everyone... im currently trying to achieve the same thing... it doesnt seem to be possible... any progress? i'm kinda new to Angular and i'm having a hard time tryin to figure this out. Any help would be more than welcome
I will publish what I got working next week. It is quite a different approach to what @cpboyd details in #7542.
Here's a Stackblitz of a working dynamic, nested menu: https://stackblitz.com/edit/dynamic-nested-menus?file=app%2Fapp.component.ts.
Note that the routing isn't actually hooked up.
@Splaktar just awesome man.... great work. Question: If a need to bind the actual route, it must be hard coded on rounting.ts right? ... you saved my life :)
@Mayocampo Glad that it could help, thanks for the feedback :)
Yeah, I didn't hook up the routing to be generated dynamically. That's another topic that I haven't dug into just yet :) So yes, for now I just make sure that the app-routing.module.ts
includes the routes that are specified in my config.
Here's another Stackblitz with routing hooked up plus a top level menu button bar: https://stackblitz.com/edit/dynamic-nested-topnav-menu?file=app%2Fapp.component.html
I'll be presenting on this at AngularMix on Thursday. Then I'll share my slides and some more examples (including a dynamic SideNav menu).
Hopefully I'll have time to put together the docs as well, but that will have to wait until later in November most likely.
@Splaktar I could swear I tried a similar approach using a custom components, but for some reason mine didn't propagate the "hover/expand" properly.
In any case, thanks for a working example!
@Splaktar hey man... i've found an issue trying to implement this solution... i don't know if i'm doing something wrong. I'll try to explain:
So, when i have a pretty simple one-level-menu it all works ok... but, here's the thing:
i made a service to make all the mapping (cause its kinda complex due to structure of my API data)
use that service to fill navItems (this.navItems = this._myService.MapElements(myListOfAPIData))
No compilation errors
Page load ok...
But when i try to load the menu (on some logon event) everything blows up and got this error:
"ERROR TypeError: Cannot read property 'changes' of undefined at mdMenu.webpackJsonp.../../../material/esm5/menu.es5.js.MdMenu.hover "
Have no idea whats happening there...
Heres my final json structure:
[
{
"idNav":0,
"displayName":"Inicio",
"iconName":"home",
"disabled":false,
"route":"home"
},
{
"idNav":1,
"disabled":true,
"displayName":"Gesti贸n de Elementos",
"iconName":"list",
"children":[
{
"idNav":2,
"disabled":true,
"displayName":"Gesti贸n de Men煤s",
"iconName":"view_headline",
"route":"menuManagement",
"parentId":1
},
{
"idNav":3,
"disabled":true,
"displayName":"Gesti贸n de Roles",
"iconName":"assignment",
"route":"roleManagement",
"parentId":1
},
{
"idNav":4,
"disabled":true,
"displayName":"Gesti贸n de Usuarios",
"iconName":"supervisor_account",
"route":"userManagement",
"parentId":1
},
{
"idNav":5,
"disabled":true,
"displayName":"Men煤 Hijo/Padre",
"children":[
{
"idNav":7,
"disabled":true,
"displayName":"Men煤 de 2do Nivel Hijo/Padre",
"iconName":"supervisor_account",
"children":[
{
"idNav":8,
"disabled":true,
"displayName":"Men煤 hijo de 3er Nivel",
"iconName":"supervisor_account",
"route":"userManagement",
"parentId":7
}
],
"parentId":5
}
],
"parentId":1
}
]
},
{
"idNav":6,
"disabled":true,
"displayName":"Men煤 Standalone",
"iconName":"warning"
}
]
Hope you can bring some light on this... don't know if this has something to do with a bad behaviour of @ViewChild ... Any help would be gladly received.
Thank u so much (and my apologizes if this is not the way to comunicate this issues)
@Mayocampo I am at AngularMix this week and I'm prepping for my talk on Thursday, so I don't have a ton of time to review your specific implementation issue. However, if you have an example in Stackblitz or Plunker, I can take a quick look and try to debug it. Another option would be posting to StackOverflow (and linking that question here).
Btw, I have run into this error and I forget the exact solution. I think it was related to missing fields in the config or needing a check in the template for something like *ngIf="child && child.items.length"
. It's hard for me to answer your specific case though w/o running the code.
I experienced the same error "ERROR TypeError: Cannot read property 'changes' of undefined at mdMenu.webpackJsonp.../../../material/esm5/menu.es5.js.MdMenu.hover ", related to "hover/expand", you can fix it by upgrading to the latest version of Angular Material. After upgrading my application the error went away and the "hover/expand" behavior now works as expected and demonstrated in the above stackblitz. Yes, it took me a few minutes to change all occurrences of the "md-" prefix to "mat-" prefix throughout my application and confirm no breaking changes exist.
Ah yes thank you @ebduhon! Beta.12 of the CDK and Angular Material are required as well as Angular 4.4.4.
Dear Materials team, Kindly add this example as this is one of the most wanted use case.
@Splaktar: Thank you for the example code.
@Splaktar cheers this works on Angular 5 to. Would be nice if this feature would be implemented.
I have that weird problem where submenus appear only on click (not hover) and don't autoclose. It is all reset if I close root menu. See image below.
What's frustrating is it works with documentation code ; it seems to be the dynamic generation / loop that creates problem.
Here's my code. I have tried replacing <a>
links with <buttons>
and removing all routerLink
or specific functions. What did I miss @Splaktar ?
Angular 5.2.1, cdk 5.1, Chromium 64.0.3282.119.
<nav fxShow fxHide.gt-md class="spin-menu">
<mat-menu #appMenu>
<ng-container *ngFor="let panel of panels">
<ng-container [ngSwitch]="panel.type">
<ng-container *ngSwitchCase="'submenu'">
<button
[matMenuTriggerFor]="submenu"
[ngClass]="{ 'active': hasSubpanelSelected(panel) }"
mat-menu-item>
{{ panel.title }}
<i *ngIf="panel.new" class="new">NEW</i>
<i *ngIf="panel.beta" class="beta">BETA</i>
</button>
<mat-menu #submenu>
<a *ngFor="let subpanel of panel.subpanels"
routerLink="{{ base }}/base/dashboard/{{ subpanel.value }}"
(click)="reloadPage($event)"
[ngClass]="{ 'active': selectedPanel == subpanel.value }"
mat-menu-item>
{{ subpanel.title }}
<i *ngIf="subpanel.new" class="new">NEW</i>
<i *ngIf="subpanel.beta" class="beta">BETA</i>
</a>
</mat-menu>
</ng-container>
<a *ngSwitchDefault
routerLink="{{ base }}base/dashboard/{{ panel.value }}"
(click)="reloadPage($event)"
[ngClass]="{ 'active': selectedPanel == panel.value }"
mat-menu-item>
{{ panel.title }}
<i *ngIf="panel.new" class="new">NEW</i>
<i *ngIf="panel.beta" class="beta">BETA</i>
</a>
</ng-container>
</ng-container>
</mat-menu>
<button [matMenuTriggerFor]="appMenu" mat-button>
<mat-icon>menu</mat-icon>
</button>
</nav>
Found it ! <mat-menu>
does not like ngSwitch
, you have to replace them with ngIf
.
Hi @d-damien! My submenu disappeared when hovering. I just copied your code from above like this.
<mat-menu #appMenu>
<ng-container *ngFor="let panel of panels">
<ng-container *ngIf="panel.sub.length>0">
<button [matMenuTriggerFor]="submenu" mat-menu-item>
{{ panel.text }}
</button>
<mat-menu #submenu>
<a *ngFor="let subpanel of panel.sub" mat-menu-item>
{{ subpanel.text }}
</a>
</mat-menu>
</ng-container>
<a *ngIf="!(panel.sub.length>0)" mat-menu-item>
{{ panel.text }}
</a>
</ng-container>
</mat-menu>
<button [matMenuTriggerFor]="appMenu" mat-button>
<mat-icon>menu</mat-icon>
</button>
panels: any[] = [
{
text: 'parent',
sub: [{
text: 'sub item'
}]
}
];
Take a look:
Hello @irowbin. This is all really weird. Non-reproducible bugs are the strangest. My diff indicates ngSwitch -> ngIf to be the only difference.
What I did to find a solution is to copy paste a working example (the one in the doc at "nested menu" section), then slightly modify it step by step until it works for me. Hope you can do the same. Here's my code as of now FYI :
<nav fxShow fxHide.gt-md class="spin-menu">
<mat-menu #appMenu>
<ng-container *ngFor="let panel of panels">
<ng-container *ngIf="panel.type == 'submenu'">
<button
[matMenuTriggerFor]="submenu"
[ngClass]="{ 'active': hasSubpanelSelected(panel) }"
mat-menu-item>
{{ panel.title }}
<i *ngIf="panel.new" class="new">NEW</i>
<i *ngIf="panel.beta" class="beta">BETA</i>
</button>
<mat-menu #submenu>
<a *ngFor="let subpanel of panel.subpanels"
routerLink="{{ base }}/base/dashboard/{{ subpanel.value }}"
(click)="reloadPage($event)"
[ngClass]="{ 'active': selectedPanel == subpanel.value }"
mat-menu-item>
{{ subpanel.title }}
<i *ngIf="subpanel.new" class="new">NEW</i>
<i *ngIf="subpanel.beta" class="beta">BETA</i>
</a>
</mat-menu>
</ng-container>
<a *ngIf="panel.type != 'submenu'"
routerLink="{{ base }}base/dashboard/{{ panel.value }}"
(click)="reloadPage($event)"
[ngClass]="{ 'active': selectedPanel == panel.value }"
mat-menu-item>
{{ panel.title }}
<i *ngIf="panel.new" class="new">NEW</i>
<i *ngIf="panel.beta" class="beta">BETA</i>
</a>
</ng-container>
</mat-menu>
<button [matMenuTriggerFor]="appMenu" mat-button>
<mat-icon>menu</mat-icon>
Menu
</button>
</nav>
Data looks like this :
"panels": [
{
"value": "1",
"title": "1"
},
{
"title": "2",
"type": "submenu",
"subpanels": [
{
"value": "2.1",
"title": "2.1"
},
{
"value": "2.2",
"title": "2.2"
}
]
},
I have the same hovering issue:
Here it is in live:
https://qpi.github.io/sightseeing/route
The source is here:
https://github.com/qpi/sightseeing/blob/master/src/app/route/route.component.html
Simplified working example.
Somewhat annoying to use ng-container
and having to create the button twice - once with and once without the [matMenuTriggerFor]
.
let links = [
{ label: "Link 1", route: "/link1"},
{ label: "Link 2", route: "/link2", children: [
{ label: "Child Link 1", route: "/child1"},
{ label: "Child Link 2", route: "/child2"}
]}
]
<mat-menu #appMenu="matMenu">
<ng-container *ngFor="let link of links">
<!-- subMenu is created regardless as it causes an error even if *ngIf is found false -->
<mat-menu #subMenu="matMenu">
<button mat-menu-item
*ngFor="let childLink of link.children"
[routerLink]="childLink.route">{{childLink.label}}</button>
</mat-menu>
<!-- Button with submenu -->
<button *ngIf="link.children" mat-menu-item
[matMenuTriggerFor]="subMenu"
[routerLink]="link.route">{{link.label}}</button>
<!-- Button without submenu -->
<button *ngIf="!link.children" mat-menu-item
[routerLink]="link.route">{link.label}}</button>
</ng-container>
</mat-menu>
<button id="nav-toggle" mat-icon-button [matMenuTriggerFor]="appMenu">
Menu
</button>
Just throwing this here for others -- a proper solution would be much nicer :)
@Splaktar Thanks for the example. Are there any plan to support the hover event on latest version, so we don't have to stick to angular 4.4.4 for the solution?
@sashagrunge which version are you using? The hover issue was fixed in Angular Material 5.2.4.
@Splaktar YEP, there is no issues. @sashagrunge You should update to latest version.
@Splaktar @irowbin I am using the latest version 5.2.4, but I realised that hover doesn't work only if I add one more component to wrap the menu-item. Thank!
I have expanded this menu to use dynamic click function as well as render checkbox control. Do you know how do I bind that to a variable via ngModel to that checkbox or set initial value of checkbox that is dependent on the variable and then change variable when checkbox state changes?
@amoguilner I haven't played with that advanced use case yet. It sounds quite interesting. It would be helpful if you had a Stackblitz to look at. I would also guess that Reactive Forms would be more capable for something like this compared to Template Driven Forms.
@sashagrunge Please post a Stackblitz demo of what you are referring to.
It is appeared to be quite tricky to implement model driven recursive menu in angular material. Quite a few examples out there are not compatible with angular past 8.0. This issue is discussed here: #16457 in details.
For those looking for an example working with 9+ angular, @Harpush has pointed to this https://stackblitz.com/edit/dynamic-material-menu-angular-8 in his comment here.
@Mayocampo Glad that it could help, thanks for the feedback :)
Yeah, I didn't hook up the routing to be generated dynamically. That's another topic that I haven't dug into just yet :) So yes, for now I just make sure that the
app-routing.module.ts
includes the routes that are specified in my config.Here's another Stackblitz with routing hooked up plus a top level menu button bar: https://stackblitz.com/edit/dynamic-nested-topnav-menu?file=app%2Fapp.component.html
I'll be presenting on this at AngularMix on Thursday. Then I'll share my slides and some more examples (including a dynamic SideNav menu).
Hopefully I'll have time to put together the docs as well, but that will have to wait until later in November most likely.
Many thanks for your examples, in the recursive nested menu, routerLinkActive
is not working, how can I add a active
class?
@Mayocampo Glad that it could help, thanks for the feedback :)
Yeah, I didn't hook up the routing to be generated dynamically. That's another topic that I haven't dug into just yet :) So yes, for now I just make sure that theapp-routing.module.ts
includes the routes that are specified in my config.
Here's another Stackblitz with routing hooked up plus a top level menu button bar: https://stackblitz.com/edit/dynamic-nested-topnav-menu?file=app%2Fapp.component.html
I'll be presenting on this at AngularMix on Thursday. Then I'll share my slides and some more examples (including a dynamic SideNav menu).
Hopefully I'll have time to put together the docs as well, but that will have to wait until later in November most likely.Many thanks for your examples, in the recursive nested menu,
routerLinkActive
is not working, how can I add aactive
class?
I have solved my own problem, take a look at the link below.
https://ng-matero.github.io/ng-matero/dashboard (switch top navigation to check the recursive nested menu)
Most helpful comment
Here's a Stackblitz of a working dynamic, nested menu: https://stackblitz.com/edit/dynamic-nested-menus?file=app%2Fapp.component.ts.
Note that the routing isn't actually hooked up.