Skip to main content

How to create a drop-down menu recursively using angular material button

Angular Material is the "Material Design" approach to making web apps in Angular. I have tried out some of this and it seems very easily doable. But I did find it difficult to get to the right way of doing things due to a lack of proper working examples. The documentation is very limited because there is a lot more you can do outside of what's described in the documentation. One such example is what I'm about demonstrate here. 

How to recursively print menu items using angular material menu button

Assume we have a json array like so: 

[
   {
      "key":"something.front_page",
      "title":"Home",
      "link":"http://dev.something.com/",
   },
   {
      "key":"78dc07e5-399a-46b3-86a5-f7141c55579e",
      "title":"Articles",
      "link":"/articles",
      "below":[
         {
            "key":"a7f279d6-7664-4adf-8a0f-25da95502143",
            "title":"Grid",
            "link": "/articles/grid",
            
         },
         {
            "key":"456b0042-eb0f-4bf9-8d9a-a6c85d3e9e71",
            "title":"List",
            "link": "/articles/list",
         }
      ]
   }
]

This is basically a Drupal menu structure. A submenu item is the "below" item name in the JSON. So what I want to do is, recursively go through the menu items and if there are sub-menu items then go into that and print them as well. 

First let's create a main-menu component, with the following code:  

import {Component, Input} from '@angular/core';

@Component({
  selector: 'app-main-menu',
  templateUrl: './main-menu.component.html',
  styleUrls: ['./main-menu.component.scss']
})
export class MainMenuComponent implements OnInit {
  menuArray: any[]; // Your json. Copy paste from above.
  constructor() {
  }

}

And your main-menu.component.html will be something like this:

<ng-container *ngFor="let item of menuArray">
        <!-- Handle main menu items here -->
        <ng-container *ngIf="item.below">
          <button mat-button [matMenuTriggerFor]="menu.buildMenu">
            {{item.title}}
          </button>
          <app-menu #menu [items]="item.below"></app-menu>
        </ng-container>
        <!-- Leaf sub menu items here -->
        <ng-container *ngIf="!item.below">
          <button mat-button color="primary" [routerLink]="item.link">
            {{item.title}}
          </button>
        </ng-container>
      </ng-container>

Here we are passing the below items to another template which takes an Input (sub menu items) and has a viewChild property. Let's create another component for that called "menu". The code for menu.component.ts is as follows: 

import {Component, Input, OnInit, ViewChild} from '@angular/core';
import {Router} from '@angular/router';

@Component({
  selector: 'app-menu',
  templateUrl: './menu.component.html',
  styleUrls: ['./menu.component.scss']
})
export class MenuComponent implements OnInit {
  @Input() items: any[];
  @ViewChild('buildMenu', {static: true}) public buildMenu;

  constructor(public router: Router) {
  }

  ngOnInit() {
  }
}

 

The template is as follows: 
 

<mat-menu #buildMenu="matMenu" [overlapTrigger]="false">
  <ng-container *ngFor="let item of items">
    <!-- Handle main menu items -->
    <ng-container *ngIf="item.below">
      <button mat-menu-item color="primary" [matMenuTriggerFor]="menu.buildMenu">
        {{item.title}}
      </button>
      <app-menu #menu [items]="item.below"></app-menu>
    </ng-container>
    <!-- Handle sub menu items -->
    <ng-container *ngIf="!item.below">
      <button mat-menu-item [routerLink]="item.link">
        {{item.title}}
      </button>
    </ng-container>
  </ng-container>
</mat-menu>

The name of the component is "app-menu" which we call within itself. So as long as item.below (sub-menu items) exists, the template continues to call itself and building a drop-down menu.

This is how it should finally look like: 

Sub-Menu

And that's it. Recursion is awesome. 

Share this post