Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create a dynamic menu using the Angular CDK Menu

I'm working on building an application menubar-style menu using the new Angular CDK Menu. I want to dynamically build this menu from a given JSON structure rather than hardcoding the menu and each submenu as shown in the docs examples. Since I want to dynamically build a menu, I'm not sure how to do it since I can't dynamically create template reference variables that I can then reference with the menu trigger.

I've tried doing a recursive component method, but that doesn't fully work, as the menus don't close when mousing to the next menu. I believe it's because once they're inside the component, the menu items are no longer direct siblings which makes it so they don't properly register in terms of auto closing when a sibling opens. I have a StackBlitz I've been working on that has my attempt and it shows the behavior of not closing the previous menu when mousing to another menu item. I've read the menu docs multiple times trying to see if I missed anything, and the CDK Menu is so new (just released a few weeks ago) that there's not really anything on the internet in the way of example usage.

Basically, what the StackBlitz shows, I have my menubar definition in the AppComponent defining the cdkMenuBar, like so:

<div class="titlebar" cdkMenuBar>
  <app-menu *ngFor="let item of menu" [item]="item" [isRoot]="true"></app-menu>
</div>

Then I have the actual menu component which is used recursively, like so:

<button
  class="menu-item-button"
  cdkMenuItem
  [cdkMenuTriggerFor]="menu"
  [class.menubar-button]="isRoot"
>
  <div>{{ item.label }}</div>
  <div *ngIf="!isRoot && item.type === 'submenu'">&gt;</div>
</button>

<ng-template #menu>
  <div class="menu-dropdown" cdkMenu>
    <ng-container *ngFor="let subItem of item.submenu">
      <ng-container *ngIf="subItem.type !== 'separator'; else separator">
        <app-menu
          [item]="subItem"
          *ngIf="subItem.type === 'submenu'; else menuItem"
        ></app-menu>
        <ng-template #menuItem>
          <button class="menu-item-button" cdkMenuItem>
            <div>{{ subItem.label }}</div>
            <div class="text-color-tertiary">
              {{ subItem.accelerator }}
            </div>
          </button>
        </ng-template>
      </ng-container>
      <ng-template #separator>
        <hr />
      </ng-template>
    </ng-container>
  </div>
</ng-template>

That basically iterates through the "submenu" items in the menu template and then for each one invokes the menu component recursively. Eventually there is no submenu and the recursion ends there.

As I mentioned above, I believe the cause may be related to the menu items not being direct siblings which possibly makes it so the Angular CDK doesn't know to close those ones. I am hoping there's a way to make this dynamic usecase work.

like image 787
John Woodruff Avatar asked Dec 17 '25 05:12

John Woodruff


1 Answers

If you don't exactly follow the ng-template[#menu] > [cdkMenu] > [cdkMenuItem] hierarchy, the menu won't work as expected.

So you can't wrap [cdkMenu] or [cdkMenuItem] with app-menu without taking some steps to restore the required DOM structure.

You could solve the nesting issue by directly referencing menuComponent.menu in the parent component's [cdkMenuTriggerFor]:

app.component.html

<button [cdkMenuTriggerFor]="menuComponent.menu">menu</button>

<app-menu #menuComponent [items]="menuItems"></app-menu>

menu.component.html

<ng-template #menu>
  <div cdkMenu>
    <ng-container *ngFor="let item of items">
      <ng-container *ngIf="hasChildren(item); else leafNode">
        <button cdkMenuItem [cdkMenuTriggerFor]="subMenu" *ngIf="menuComponent.menu as subMenu">
              <div>{{ item.label }}</div>
              <div *ngIf="item.children">▸</div>
            </button>
        <app-menu #menuComponent [items]="item.children!"></app-menu>
      </ng-container>
      <ng-template #leafNode>
        <button cdkMenuItem>
              {{ item.label }}
            </button>
      </ng-template>
    </ng-container>
  </div>
</ng-template>

Working demo: https://stackblitz.com/github/rensjaspers/cdk-dynamic-menu-demo?file=src/app/app.component.html

like image 133
Rens Jaspers Avatar answered Dec 19 '25 19:12

Rens Jaspers



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!