How do you create a nested, collapsing menu with Angular Material2?

13,472

Solution 1

I know this is an old question, but for others coming upon this page looking for the same thing, as I did, here's how I took care of it with the current version of Angular Material (6.4.6) and once you get the CSS styles done right, it works beautifully.

Please note that there still is no official support for this functionality, and you have to set it up yourself, which could be done a number of ways, but I chose to use only Angular Material components.

Here's example markup with some comments, using an object for your nav links:

<mat-sidenav-container>
  <mat-sidenav #sidenav
    class="sidenav"
    [mode]="mobileQuery.matches ? 'over' : 'side'"
    [opened]="mobileQuery.matches ? false : true">
    <mat-nav-list>
      <!-- wrap all the nav items in an accordion panel -->
      <mat-accordion [displayMode]="flat">
        <div *ngFor="let navItem of navList">

          <!-- use a simple div for an item that has no children,
            match up the styling to the expansion panel styles -->
          <div class="nav-head" *ngIf="navItem.pages.length === 0">
            <a class="nav-link"
              [routerLink]="navItem.link"
              routerLinkActive="selected"
              (click)="closeSidenav()">
              <mat-icon>{{navItem.icon}}</mat-icon>
              <span class="nav-link-text">{{navItem.heading}}</span>
            </a>
          </div>

          <!-- use expansion panel for heading item with sub page links -->
          <mat-expansion-panel *ngIf="navItem.pages.length > 0"
            class="mat-elevation-z0">
            <mat-expansion-panel-header class="nav-head" [expandedHeight]="'48px'">
              <mat-panel-title class="nav-link">
                <mat-icon>{{navItem.icon}}</mat-icon>
                <span class="nav-link-text">{{navItem.heading}}</span>
              </mat-panel-title>
            </mat-expansion-panel-header>
      
            <div class="nav-section">
              <!-- loop through all your sub pages inside the expansion panel content -->
              <div *ngFor="let navPage of navItem.pages"
                class="nav-item">
                <a class="nav-link"
                  [routerLink]="navPage.link"
                  routerLinkActive="selected"
                  (click)="closeSidenav()">{{navPage.title}}</a>
              </div>
            </div>
          </mat-expansion-panel>
        </div>
      </mat-accordion>
    </mat-nav-list>
  </mat-sidenav>
  <mat-sidenav-content>
    <div class="container-fluid">
      <router-outlet></router-outlet>
    </div>
  </mat-sidenav-content>
</mat-sidenav-container>

EDIT: To answer a couple of additional questions that were made, mobileQuery is coming from the Angular Material CDK which adds some helpers for detecting mobile breakpoints inside your components. See here: Angular Material CDK Layout

Also, my component file really doesn't do anything in this case other than pull the correct nav object to be displayed from a service, but here is an example of how I set up the objects (of course they could be whatever you need them to be)

[
  {
    heading: 'Dashboard',
    icon: 'dashboard',
    link: '/dashboard',
    pages: []
  },
  {
    heading: 'Main Heading',
    icon: 'settings',
    link: '/settings',
    pages: [
      {
        title: 'Subpage',
        link: '/settings/advanced',
        icon: ''
      }
    ]
  }
]

Solution 2

Unfortunately, as of now, the material design library doesn't provide any tool to do what you want.

Once it comes out, you may be able to use the tree component to achieve what you want. Otherwise, you should look to build one yourself. I have done so myself (in a proprietary codebase, unfortunately) and I'd be glad to help with specific questions. I used nested <md-list>s, <button md-icon-button>s and custom animations to do so.

Share:
13,472

Related videos on Youtube

Stuart
Author by

Stuart

15 years an elementary school teacher, 7 years a contractor for an MS gold partner, now developing big data tooling for corporate America.

Updated on June 16, 2022

Comments

  • Stuart
    Stuart almost 2 years

    I am looking for support within Angular Material2 for nested menus within a sidebar. The top level would typically be closed by default, and opening a top level would expose nested menu items.

    I thought this made sense as a starting point, but the child nav items render (poorly) outside of the parent items:

    plnkr

    <md-sidenav-container class="my-container">
      <md-sidenav #sidenav class="my-sidenav">
        <md-list>
            <md-list-item>
              <h3 md-line> First Parent </h3>
              <md-nav-list>
                <a md-list-item href="#">First Child</a>
                <a md-list-item href="#">Second Child</a>
                <a md-list-item href="#">Third Child</a>
              </md-nav-list>
            </md-list-item>
            <md-list-item>
              <h3 md-line> Second Parent </h3>
              <md-nav-list>
                <a md-list-item href="#">First Child</a>
                <a md-list-item href="#">Second Child</a>
              </md-nav-list>
            </md-list-item>
        </md-list>
      </md-sidenav>  
      <div class="my-container">
        <button md-button (click)="sidenav.open()">Open</button>
      </div>
    </md-sidenav-container>
    

    Has anyone created this kind of sidebar menu with @angular/material?