[Fixed] Should I nest *ngFor inside *ngFor for a dynamic menu/category?

Issue

I want to make a dynamic menu that come from my API so I can add/remove category from a "dashboard" in my app.

For the moment I have a hardcoded HTML as below

<button mat-button class="extras" (click)="toggleDropdown('clothing')">CLOTHINGS <span class="additional">+</span></button>
    <div [ngClass]="{'show' : shows.clothing}" class="dropdown clothing">
        <button mat-menu-item routerLink="category/clothing">All</button>
        <button mat-menu-item>Corsets</button>
        <button mat-menu-item class="extras" (click)="toggleDropdown('dresses')">Dresses<span class="additional">+</span></button>
            <div [ngClass]="{'show' : shows.dresses}" class="dropdown dresses">
                <button mat-menu-item> Maxi Dresses</button>
                <button mat-menu-item> Midi Dresses</button>
                <button mat-menu-item> Mini Dresses</button>
            </div>
        <button mat-menu-item>Denim</button>
        <button mat-menu-item>Tops</button>
        <button mat-menu-item class="extras" (click)="toggleDropdown('bottoms')">Bottoms<span class="additional">+</span></button>
            <div [ngClass]="{'show' : shows.bottoms}" class="dropdown bottoms">
                <button mat-menu-item>Trousers</button>
                <button mat-menu-item>Skirts</button>
                <button mat-menu-item>Shorts</button>
                <button mat-menu-item>Playsuits</button>
            </div>
        <button mat-menu-item>Loungewear</button>
        <button mat-menu-item>Outerwear</button>
        <button mat-menu-item>Sweats</button>
    </div>

<button mat-button class="extras" (click)="toggleDropdown('collections')">BY COLLECTIONS <span class="additional">+</span></button>
    <div [ngClass]="{'show' : shows.collections}" class="dropdown collections">
        <button mat-menu-item routerLink="category/collections">All</button>
        <button mat-menu-item>Summer Collection</button>
        <button mat-menu-item>Winter Collection</button>
        <button mat-menu-item>Spring Collection</button>
        <button mat-menu-item>Fall Collection</button>
    </div>

<button mat-button routerLink="accessories">ACCESSORIES</button>

I created a JSON for the menu so I would display it instead of this hard coded html

export const menu = [
    {
        name: "clothings",
        children: [
            { name: "all", path: "clothing/all"},
            { name: "corsets", path: "clothing/corsets"},
            { name: "dresses", children: [
                { name:"maxi dresses", path: "clothing/dresses/maxi-dresses"},
                { name:"midi dresses", path: "clothing/dresses/midi-dresses"},
                { name:"mini dresses", path: "clothing/dresses/mini-dresses"}
            ]},
            { name: "denim", path: "clothing/denim"},
            { name: "tops", path: "clothing/tops"},
            { name: "bottoms", children: [
                { name:"trousers", path: "clothing/bottoms/trousers"},
                { name:"skirts", path: "clothing/bottoms/skirts"},
                { name:"shorts", path: "clothing/bottoms/shorts"},
                { name:"playsuits", path: "clothing/bottoms/playsuits"},
            ]},
            { name: "loungewear", path: "clothing/loungewear"},
            { name: "outerwear", path: "clothing/outerwear"},
            { name: "sweats", path: "clothing/sweats"}
         ],
    },
    {
        name: "by collection",
        children: [
            { name: "all", path: "collection/all"},
            { name: "summer collection", path: "collection/summer-collection"},
            { name: "winter collection", path: "collection/winter-collection"},
            { name: "spring collection", path: "collection/spring-collection"},
            { name: "fall collection", path: "collection/fall-collection"}
        ]
    },
    {
        name: "accessories",
        children: [
            { name: "all", path: "accessories/all"},
            { name: "glasses", path: "accessories/glasses"},
            { name: "necklace", path: "accessories/necklace"},
        ]
    },
]

I tried this for the moment

 <div *ngFor="let category of menu">
        <button mat-button class="extras" (click)="toggleDropdown(category.name)">{{category.name}}<span class="additional">+</span></button>
        <div *ngFor="let child of category.children">
            <div [ngClass]="{'show': shows[category.name]}" class="dropdown {{category.name}}">
                <button mat-menu-item (click)="child.children && toggleDropdown(child.name)">{{child.name}}</button>
                <div *ngFor="let subchild of child.children">
                    <div [ngClass]="{'show' : shows[subchild.name]}" class="dropdown {{subchild.name}}">
                        <button mat-menu-item>{{subchild.name}}</button>
                    </div>
                </div>
            </div>
        </div>
    </div>

This displays correctly the parents category and their childs ("clothing", "by collection" and "accessories" display its children based on the ngClass

But the subchildren of their childre doesn’t work.

I wonder if I am using the correct way to go because it’s seems to me a bit confusing all of these *ngFor

Solution

Try using the ‘matMenuTriggerFor’ attribute of the mat-menu, for your nested menu options, instead of using a nested for loop.

<mat-menu #myMenu="matMenu">
        <!-- menu option without sub menus -->
        <button mat-menu-item (click)='someFunction()'>Call function</button> 
        <!-- menu option with sub menus -->
        <button mat-menu-item [matMenuTriggerFor]="subMenu">Show sub menu</button>
      </mat-menu>

      <mat-menu #subMenu="matMenu">
          <button mat-menu-item>subOpt1</button>
          <button mat-menu-item>subOpt2</button>
      </mat-menu>

So because you have nested dynamic menus, you can render each of the dynamic sub-menus separately using different tags and ngFor loops. Finally, you can link a sub menu to a main menu, by using the ‘matMenuTriggerFor’ attribute

You may view more examples at the below link :
https://material.angular.io/components/menu/overview#menu-nested

Leave a Reply

(*) Required, Your email will not be published