Search code examples
angularperformanceangular-ng-if

Efficient approach to nested navigation structure in Angular


Let's assume I have the following tree structure:

    Item1            Item2            Item3            Item4             Itemn  
  /   |   \        /   |   \        /   |   \        /   |   \         /   |   \
 /    |    \      /    |    \      /    |    \      /    |    \       /    |    \
 a    b    c      d    e    f      g    h     i     j    k     l     n-2   n-1   n

Each node from a,b,...,n has values. What's a more efficient way to structure this navigation?

My approach is he following:

Create an array toggleStatus that contains boolean values. Assign a number to each tab which is respectively equal to the index of toggleStatus. A method toggle(index) will toggle the value of toggleStatus at index index.

Please have a look at my Stackblitz for a working example.


Solution

  • You don't need to add all the data directly into your HTML. You can just create an array of objects which will make it perfect for a nested ngFor case.

    Your data will look something like this

    menus: any[] = [{
        item: 'Item 1', submenus: [
            { item: 'a', info: 'Info of a' },
            { item: 'b', info: 'Info of b' },
            { item: 'c', info: 'Info of c' }
        ]
    }, {
        item: 'Item 2', submenus: [
            { item: 'd', info: 'Info of d' },
            { item: 'e', info: 'Info of e' },
            { item: 'f', info: 'Info of f' }
        ]
    }, {
        item: 'Item 3', submenus: [
            { item: 'g', info: 'Info of g' },
            { item: 'h', info: 'Info of h' },
            { item: 'i', info: 'Info of i' }
        ]
    }, {
        item: 'Item 4', submenus: [
            { item: 'j', info: 'Info of j' },
            { item: 'k', info: 'Info of k' },
            { item: 'l', info: 'Info of l' }
        ]
    }];
    

    Then in your HTML, we will loop over the menus using ngFor

    <div *ngFor="let menu of menus; let i = index">
        <!-- Menu -->
        <div (click)="toggleMenu(i)" class="text-white cursor-pointer text-center">
            <div class="py-4 bg-blue-600 hover:bg-blue-400">
                {{menu.item}}
            </div>
            <div class="container" *ngIf="showSubmenu[i]">
                <!-- Submenus -->
                <ng-container *ngFor="let submenu of menu.submenus; let j = index">
                    <div (click)="toggleSubmenu($event, submenu.item)" class="py-3 w-1/3 inline-block"
                        [ngClass]="{'bg-blue-200': j === 0, 'bg-blue-300': j === 1, 'bg-blue-400': j === 2}">
                        {{submenu.item}}
                    </div>
                </ng-container>
                <!-- Information -->
                <div *ngFor="let submenu of menu.submenus; let j = index">
                    <div *ngIf="showInfo[submenu.item]" (click)="$event.stopPropagation()" class="py-3 bg-green-400">
                        {{submenu.info}}
                    </div>
                </div>
            </div>
        </div>
    </div>
    

    In your component, we shall define the flags and the toggle functions. I have used an array for flags. We will dynamically insert the toggle flag into this array based on the index. $event.stopPropagation is used to prevent the click event from bubbling up into the parent element's click event. Here's how the component will look

    showSubmenu: any[] = [];
    showInfo: any[] = [];
    
    toggleMenu(index: number) {
        this.showSubmenu[index] = !this.showSubmenu[index];
    }
    
    toggleSubmenu(event: MouseEvent, item: string) {
        event.stopPropagation();
        this.showInfo[item] = !this.showInfo[item];
    }
    

    Note: The item passed to thetoggleSubmenu should be a unique value. If you have an id, it would be preferred to use that here instead of item.

    Here is a working StackBlitz of the same.