ShadCn UI kit now on producthunt
Skip to content
UI Lib Blog

How to Create a Angular multi level Menu

Multi level or unlimited level menu are commonly used in admin panel. MatX Angular dashboard has unlimited level menu. MatX is a Free Angular material design admin dashboard template. This side navigation of MatX has unlimited level menu. Let’s take a look at the data structure of this menu.

File on github: navigation.service.ts

iconMenu: IMenuItem[] = [{
      name: 'WIZARD',
      state: 'forms/wizard',
      type: 'link',
      icon: 'grain',
    },
    {
      name: 'Multi Level',
      type: 'dropDown',
      tooltip: 'Multi Level',
      icon: 'format_align_center',
      sub: [
        { name: 'Level Two', state: 'fake-4' },
        {
          name: 'Level Two',
          type: 'dropDown',
          sub: [
            { name: 'Level Three', state: 'fake-2' },
            { 
              name: 'Level Three', 
              type: 'dropDown',
              sub: [
                { name: 'Level Four', state: 'fake-3' },
                { 
                  name: 'Level Four', 
                  type: 'dropDown',
                  sub: [
                    { name: 'Level Five', state: 'fake-3' },
                    { name: 'Level Five', type: 'link', state: 'fake-3' }
                  ]
                }
              ]
            }
          ]
        },
        { name: 'Level Two', state: 'fake-5' }
      ]
    }]


Dropdown menu has type: 'dropDown' and a sub: [] property, sub contains the child items.

The Logic

Inside the HTML template we have the logic. Menu items array is coming from items props of the component.

File on github: sidenav.component.ts

@Input('items') public menuItems: any[] = [];

sidenav.template.html

<div class="sidenav-hold" #sidenav>

  <ng-container *ngTemplateOutlet="menuTemplate; context: {menuItems: menuItems}"></ng-container>

</div>

<ng-template #menuTemplate let-menuItems="menuItems">
  <ul appDropdown class="sidenav">
    <li *ngFor="let item of menuItems" appDropdownLink routerLinkActive="open">
      

      <!-- Item -->
      <!-- MENU ITEM -->
      <div *ngIf="!item.disabled && item.type !== 'separator' && item.type !== 'icon'" class="lvl1">
        <a routerLink="/{{item.state}}" appDropdownToggle matRipple
          *ngIf="item.type !== 'extLink' && item.type !== 'dropDown'" routerLinkActive="open">
          <mat-icon *ngIf="item.icon" class="sidenav-mat-icon">{{item.icon}}</mat-icon>
          <mat-icon *ngIf="item.svgIcon" [svgIcon]="item.svgIcon" class="svgIcon"></mat-icon>
          <span class="item-name lvl1">{{item.name | translate}}</span>
          <span fxFlex></span>
          <span class="menuitem-badge mat-bg-{{ badge.color }}" [ngStyle]="{background: badge.color}"
            *ngFor="let badge of item.badges">{{ badge.value }}</span>
        </a>

        <!-- DropDown -->
        <a *ngIf="item.type === 'dropDown'" appDropdownToggle matRipple>
          <mat-icon *ngIf="item.icon" class="sidenav-mat-icon">{{item.icon}}</mat-icon>
          <mat-icon *ngIf="item.svgIcon" [svgIcon]="item.svgIcon" class="svgIcon"></mat-icon>
          <span class="item-name lvl1">{{item.name | translate}}</span>
          <span fxFlex></span>
          <span class="menuitem-badge mat-bg-{{ badge.color }}" [ngStyle]="{background: badge.color}"
            *ngFor="let badge of item.badges">{{ badge.value }}</span>
          <mat-icon class="menu-caret">keyboard_arrow_right</mat-icon>
        </a>

        <div *ngIf="item.type === 'dropDown'">
          <ng-container *ngTemplateOutlet="menuTemplate; context: {menuItems: item.sub}"></ng-container>
        </div>

      </div>
    </li>
  </ul>
</ng-template>

We used ng-template to render the dropdown menu. We called it #menuTemplate. We passed the array of menu items to the ng-template.


We iterate through the array of menu items and render each item. Then we used *ngIf to conditionally render different types of menu items such as link, dropdown, extLink.
If the item type is dropDown we render the dropdown toggle item first. Then the child menu items directly underneath of it.


And here is the tricky part. We called ng-template #menuTemplate inside itself. Means it’s calling or referencing itself recursively. And we passed the array of submenu or child items. It renders the child items inside itself.

And again if it finds another dropDown during rending the child items it will again call the ng-template inside child items and render grandchild items.This process will go on as long as it finds the child items.


Expansion panel

This is how the rendering works. Now let’s talk about how expansion panel works. We created few directives. appDropdown directive which is attached to the ul tag of menu.
appDropdownLink which is attached to the li tag of an item
and the appDropdownToggle toggle which is attached to the a tag of an item.
These three directive does one job. Add and remove open class to the li tag if you click on it.


CSS

We need a little bit of css to make the drop down Menu actually open and close on click. By default the ul tag of child menu is set to max-height: 0, when the open class added by clicking the its parent item we set max-height to a big number like 1000px. We assumed the height of the child menu will be always less than 1000px.You can always increase this number. Here is the CSS code example