Create Dynamic Menu in Angular v17 with Angular Material v17


Author: Gautam Dayal
Published On: Tuesday, May 21, 2024


Follow the following steps to create Dynamic Menu in Angular v17 with Angular Material v17 with auto hide last open menu.

image

1. Install Angular Cli v17.3.0

npm i @angular/cli@17.3.0

2. Create new angular project dynamic_menu

ng new dynamic_menu --no-standalone

3. Open the created project folder

cd dynamic_menu

4. Add Angular Material v17.3.7

ng add @angular/material@17.3.7

5. Generate Categories Menu component

ng g c categories-menu

6. Create new file categoriesMenu.ts with the below code:

export class CategoriesMenu {
    name: string = '';
    children: CategoriesMenu[] = [];
}

7. Open categories-menu.component.ts file and add the below code:

import { Component, Input, Output, EventEmitter } from '@angular/core';
import { MatMenuTrigger } from '@angular/material/menu';
import { CategoriesMenu } from '../categoriesMenu';

@Component({
  selector: 'app-categories-menu',
  templateUrl: './categories-menu.component.html',
  styleUrl: './categories-menu.component.scss'
})
export class CategoriesMenuComponent {
  @Input('menuItem')
  menuItem!: CategoriesMenu;

  @Output()
  menuTriggerEvent = new EventEmitter<MatMenuTrigger>();

  subMenuTrigger!: MatMenuTrigger;

  constructor() { }

  handleMenuTrigger(trigger: MatMenuTrigger) {
    trigger.openMenu();
    this.menuTriggerEvent.emit(trigger);
  }

  handleSubMenuTrigger(trigger: MatMenuTrigger) {
    if (this.subMenuTrigger && this.subMenuTrigger !== trigger) {
      this.subMenuTrigger.closeMenu();
    }

    if (this.subMenuTrigger !== trigger) {
      trigger.focus();
      trigger.openMenu();
    }

    this.subMenuTrigger = trigger;
  }
}

8. Open categories-menu.component.html file and add the below html:

<ng-container *ngIf="menuItem.children && menuItem.children.length > 0">
    <a mat-button mat-menu-item class="mainContainerColor" [matMenuTriggerFor]="subMenu"
        #subMenuTrigger="matMenuTrigger" (mouseover)="handleMenuTrigger(subMenuTrigger);">{{menuItem.name}}</a>
    <mat-menu #subMenu="matMenu">
        <ng-container>
            <app-categories-menu [menuItem]="item" (menuTriggerEvent)="handleSubMenuTrigger($event)"
                *ngFor="let item of menuItem.children"></app-categories-menu>
        </ng-container>
    </mat-menu>
</ng-container>
<ng-container *ngIf="!(menuItem.children && menuItem.children.length > 0)">
    <a mat-button mat-menu-item class="mainContainerColor">{{menuItem.name}}</a>
</ng-container>

9. Open app.module.ts file and add the below code:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';

import { MatInputModule } from '@angular/material/input';
import { MatIconModule } from '@angular/material/icon';
import { MatMenuModule } from '@angular/material/menu';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { CategoriesMenuComponent } from './categories-menu/categories-menu.component';

@NgModule({
  declarations: [
    AppComponent,
    CategoriesMenuComponent
  ],
  imports: [
    MatInputModule,
    MatIconModule,
    MatMenuModule,
    BrowserAnimationsModule,
    BrowserModule,
    AppRoutingModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

10. Open app.component.ts and add the below code:

import { Component } from '@angular/core';
import { MatMenuTrigger } from '@angular/material/menu';
import { CategoriesMenu } from './categoriesMenu';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrl: './app.component.scss'
})
export class AppComponent {
  title = 'dynamic_menu';

  subMenuTrigger!: MatMenuTrigger;
  categoriesMenu: CategoriesMenu = {
    name: 'menu item 1',
    children: [{
      name: 'child menu 1',
      children: [{
        name: 'grandchild menu 1',
        children: []
      }, {
        name: 'grandchild menu 2',
        children: []
      }, {
        name: 'grandchild menu 3',
        children: []
      }]
    }, {
      name: 'child menu 2',
      children: [{
        name: 'grandchild menu 4',
        children: []
      }, {
        name: 'grandchild menu 5',
        children: []
      }, {
        name: 'grandchild menu 6',
        children: []
      }]
    }]
  };

  handleSubMenuTrigger(trigger: MatMenuTrigger) {
    if (this.subMenuTrigger && this.subMenuTrigger !== trigger) {
      this.subMenuTrigger.closeMenu();
    }

    if (this.subMenuTrigger !== trigger) {
      trigger.focus();
      trigger.openMenu();
    }

    this.subMenuTrigger = trigger;
  }
}

11. Open app.component.html file and add the below code:

<div>
  <mat-label>Choose</mat-label>
</div>
<div *ngIf="categoriesMenu">
  <button mat-button [matMenuTriggerFor]="menu" #menuTrigger="matMenuTrigger"
      style="cursor: pointer;">
      <mat-label [ngClass]="['buttonLabel']">{{categoriesMenu.name}}</mat-label>
      <mat-icon [ngClass]="['optionButtonIcon']">keyboard_arrow_down</mat-icon>
  </button>
  <mat-menu #menu="matMenu">
      <ng-container>
          <app-categories-menu [menuItem]="item"
              (menuTriggerEvent)="handleSubMenuTrigger($event)"
              *ngFor="let item of categoriesMenu.children"></app-categories-menu>
      </ng-container>
  </mat-menu>
</div>

12. Run the app:

npm run start

The output will look like below:

image