Home > OS >  Open sidebar menu in angular with *ngIf and *ngFor
Open sidebar menu in angular with *ngIf and *ngFor

Time:10-21

I know it's supposed to be simple but I'm trying to find a nice way to do the following:

I have a menu in my sidebar and I want that by clicking on a specific span it will not rout me somewhere but will show me another menu within it. Below is the code:

sidebar.html

<div *ngFor="let item of items">
  <span>{{item.label}}
<div *ngIf="isClicked" *ngFor="let nestedItem of item.nestedItems" class="nav-nestedItem-label">
  {{nestedItem.label}}
</div>

sidebar.ts

      this.items = [
    { label: 'A', image: 'dashboard', route: 'AAA' },
    { label: 'B', image: 'my-apps', route: 'BBB' },
    { label: 'C', image: 'app-store', route: 'CCC' },
    { label: 'D', image: 'data', route: 'DDD' },
    { label: 'E', image: 'commercial-tools', route: 'EEE' },
    { label: 'F', image: 'dev-tools', route: 'FFF', },
    { label: 'G', image: 'data-source-mng', route: '',
        nestedItems: [{label: 'GA'},
                      {label: 'GB'},
                      {label: 'GC'}]},
    
  ];

I want the nestedItems to open for me by pressing lable 'G' and it will nor rout to anywhere.

He is currently showing me the entire menu including the nestedItems

How can this be arranged in a beautiful and non-primitive way? I did some way but the code is long and cumbersome. I need a beautiful way

Thank you!!!

CodePudding user response:

You can try something like this:

items = [
    { label: 'A', image: 'dashboard', route: 'AAA' },
    { label: 'B', image: 'my-apps', route: 'BBB' },
    { label: 'C', image: 'app-store', route: 'CCC' },
    { label: 'D', image: 'data', route: 'DDD' },
    { label: 'E', image: 'commercial-tools', route: 'EEE' },
    { label: 'F', image: 'dev-tools', route: 'FFF', },
    { label: 'G', image: 'data-source-mng', route: '',
        nestedItems: [{label: 'GA', route: 'GAAA'},
                      {label: 'GB', route: 'GBBB'},
                      {label: 'GC', route: 'GCCC'}]},
    
  ];

  myClick(item: any, event?: any): void {
    if (item.nestedItems) {
      event.target.querySelector('div').hidden = false;
    } else {
      // redirect to...
    }
  }

and your html:

<div *ngFor="let item of items" (click)="myClick(item, $event)">
    <span>{{item.label}}
        <div hidden="true" *ngIf="item.nestedItems" class="nav-nestedItem-label">
            <div *ngFor="let nestedItem of item.nestedItems" (click)="myClick(nestedItem)">
                &nbsp;&nbsp;>&nbsp;{{nestedItem.label}}
            </div>
        </div>
    </span>
</div>

CodePudding user response:

I would store the clicked menu item in a property. You can then use *ngIf to compare the loop item with the clicked item to decide whether or not to show the menu item.

component.html

<div *ngFor="let item of items" (click)="expand(item)">
  <span>{{item.label}}</span>
  <ng-container *ngIf="item === expanded">
    <div *ngFor="let nestedItem of item.nestedItems">
      {{nestedItem.label}}
    </div>
  </ng-container>
</div>

(click)="expand(item)" handles click events on the outer div, passing in the current item as the single argument.

<ng-container> is an angular component that doesn't get rendered (also, you can't use both *ngIf and *ngFor on the same element)

*ngIf="item === expanded" only renders the <ng-container> when the expanded item is the same object as the current item.

component.ts

  items: Item[] = [
    { label: 'A', image: 'dashboard', route: 'AAA' },
    { label: 'B', image: 'my-apps', route: 'BBB' },
    { label: 'C', image: 'app-store', route: 'CCC' },
    { label: 'D', image: 'data', route: 'DDD' },
    { label: 'E', image: 'commercial-tools', route: 'EEE' },
    {
      label: 'F',
      image: 'dev-tools',
      nestedItems: [{ label: 'FA', route: 'FAAA' }],
    },
    {
      label: 'G',
      image: 'data-source-mng',
      nestedItems: [
        { label: 'GA', route: 'GAAA' },
        { label: 'GB', route: 'GBBB' },
        { label: 'GC', route: 'GCCC' },
      ],
    },
  ];

  expanded?: Item;

  expand(item: Item): void {
    this.expanded = item;
  }

I have created 2 interfaces as below. You could also declare expanded: any; without using interfaces, but my personal preference is to use types where possible.

interface Item {
  label: string;
  image?: string;
  route?: string;
  nestedItems?: SubItem[];
}

interface SubItem {
  label: string;
  route: string;
}

Outside of scope: collapsing the menu item when elements outside of the menu are clicked.

Stackblitz Demo: https://stackblitz.com/edit/angular-ivy-f1kifq?file=src/app/app.component.html

  • Related