Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs(menu): add example of nested menus using dynamic data #7434

Open
Splaktar opened this issue Sep 30, 2017 · 33 comments
Open

docs(menu): add example of nested menus using dynamic data #7434

Splaktar opened this issue Sep 30, 2017 · 33 comments
Labels
area: material/menu docs This issue is related to documentation feature This issue represents a new feature or feature request rather than a bug or bug fix help wanted The team would appreciate a PR from the community to address this issue P4 A relatively minor issue that is not relevant to core functions

Comments

@Splaktar
Copy link
Member

Bug, feature request, or proposal:

Feature Request - Menu Docs

What is the expected behavior?

The docs would include an example of building a menu from a data structure similar to the radio button docs.

What is the current behavior?

The menu docs only include a nested menu example using static HTML.

What are the steps to reproduce?

Working on a Stackblitz demo atm...

What is the use-case or motivation for changing an existing behavior?

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.

Which versions of Angular, Material, OS, TypeScript, browsers are affected?

Angular 4.4.3
TypeScript 2.3.4
Chrome 61
OS X El Capitan

Is there anything else we should know?

Thank you for your hard work on this library!

@jelbourn jelbourn added docs This issue is related to documentation feature This issue represents a new feature or feature request rather than a bug or bug fix help wanted The team would appreciate a PR from the community to address this issue P4 A relatively minor issue that is not relevant to core functions labels Oct 1, 2017
@Splaktar
Copy link
Member Author

Splaktar commented Oct 2, 2017

#6765 was likely to block this use case, but the fix has been merged into master.

@Splaktar
Copy link
Member Author

Splaktar commented Oct 2, 2017

There is an example of creating md-menu's dynamically here but it doesn't demonstrate nesting.

@Splaktar
Copy link
Member Author

Splaktar commented Oct 2, 2017

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.

@cpboyd
Copy link

cpboyd commented Oct 5, 2017

@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.

@Mayocampo
Copy link

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

@Splaktar
Copy link
Member Author

Splaktar commented Oct 7, 2017

I will publish what I got working next week. It is quite a different approach to what @cpboyd details in #7542.

@Splaktar
Copy link
Member Author

Splaktar commented Oct 8, 2017

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.

@Mayocampo
Copy link

@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 :)

@Splaktar
Copy link
Member Author

Splaktar commented Oct 9, 2017

@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.

@cpboyd
Copy link

cpboyd commented Oct 9, 2017

@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!

@Mayocampo
Copy link

@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:

  1. I took your code and extend interface with some other properties.
  2. I already have a model wich i fill with data from an API
  3. I map that model into the structure of the interface you made

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)

@Splaktar
Copy link
Member Author

Splaktar commented Oct 10, 2017

@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.

@ghost
Copy link

ghost commented Oct 13, 2017

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.

@Splaktar
Copy link
Member Author

Ah yes thank you @ebduhon! Beta.12 of the CDK and Angular Material are required as well as Angular 4.4.4.

@Smitha-Patil
Copy link

Dear Materials team, Kindly add this example as this is one of the most wanted use case.
@Splaktar: Thank you for the example code.

@Swoox
Copy link

Swoox commented Nov 28, 2017

@Splaktar cheers this works on Angular 5 to. Would be nice if this feature would be implemented.

@d-damien
Copy link

d-damien commented Feb 20, 2018

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>

image

@d-damien
Copy link

d-damien commented Feb 21, 2018

Found it ! <mat-menu> does not like ngSwitch, you have to replace them with ngIf.

@irowbin
Copy link

irowbin commented Feb 22, 2018

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:
video_001 1

@d-damien
Copy link

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>

@d-damien
Copy link

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"
        }
      ]
    },

@qpi
Copy link

qpi commented Feb 23, 2018

I have the same hovering issue:

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

@irowbin
Copy link

irowbin commented Feb 24, 2018

@qpi #10081

@CharlyRipp
Copy link

CharlyRipp commented Apr 3, 2018

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 :)

@sashagrunge
Copy link

@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?

@Splaktar
Copy link
Member Author

@sashagrunge which version are you using? The hover issue was fixed in Angular Material 5.2.4.

@irowbin
Copy link

irowbin commented Apr 11, 2018

@Splaktar YEP, there is no issues. @sashagrunge You should update to latest version.

@sashagrunge
Copy link

@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!

@amoguilner
Copy link

amoguilner commented Jun 20, 2018

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?

@Splaktar
Copy link
Member Author

Splaktar commented Jun 21, 2018

@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.

@alex-vas
Copy link

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.

@nzbin
Copy link

nzbin commented Nov 22, 2020

@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?

@nzbin
Copy link

nzbin commented Jan 9, 2021

@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?

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)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: material/menu docs This issue is related to documentation feature This issue represents a new feature or feature request rather than a bug or bug fix help wanted The team would appreciate a PR from the community to address this issue P4 A relatively minor issue that is not relevant to core functions
Projects
None yet
Development

No branches or pull requests