0

I have mat menu which needs to trigger in multiple places.

  1. Where i have repeated list items(ngFor) and on each list item we show more icon and on clicking on it will show mat menu with 3 options each on click will call one function.

  2. Here i have a button and on clicking on it will show mat menu with same 3 options each on click will call same function like the above.

Each on click function will open a mat-dialog with a form inside it.

Finally we have different menu trigger items but same mat menu.

Presently i have repeated the code in component which is bad way to do it. I have plenty of repeated code in both components. I would like to optimize it So how can we do it .

Can i create common component for different menu trigger items? (Is this possible since triggering mat-menu elements are different ) Or can i create service to call menu on click functions? Or can i create interface and put the implementation functions there and implement that interface in each component where i am using mat menu ?

Please suggest me how can i optimize it.

Roster
  • 1,764
  • 6
  • 17
  • 36

1 Answers1

1

We can create a "matmenu" component that can be feed by an array of object.

Imagine you has an array like

  menu:any[]=[
    {label:"Vertebrates",children:[
      {label:"Fishes",children:[
          {label:"Baikal oilfish",action:1},
          {label:"Bala shark",action:2},

            ]},
      {label:"Amphibians",children:[
          {label:"Sonoran desert toad",action:3},
          {label:"Western toad",action:4},
          {label:"Arroyo toad",action:5},
          {label:"Yosemite toad",action:6},

      ]},
      {label:"Reptiles",action:7},
      {label:"Birds",action:8},
      {label:"Mammals",action:9}
      ]
    },
    {label:"Invertebrates",children:[
      {label:"Insects",action:10},
      {label:"Molluscs",action:11},
      {label:"Crustaceans",action:12}
      ]
    }
    ]

You can create a component that create the "mat-menus"

export class MenuComponent implements OnInit,AfterViewInit {
  @Input() title:string
  @Input() menu:any[]
  @Output() action:EventEmitter<any>=new EventEmitter<string>()

  //we are going to get all "mat-menu" using viewChildren
  @ViewChildren(MatMenu) matmenus:QueryList<MatMenu>
  menuItems:any[]=[]
  yet:boolean=false;
  submenus:any[]=[]
  ngOnInit(){
      this.createSubmenus(this.menu,"s0",1)
      this.reindex()
  }
  ngAfterViewInit(){
    //this avoid Angular give us errors, only when repaint the menu
    //we asign the [matMenutiggerFor]
    setTimeout(()=>{
        this.yet=true
        })
    }
    //simply call to the output
    onClick(value:any)
    {
      this.action.emit(value);
    }
    //return the "mat-menu" in the index selected
    getMenu(index:number)
    {
      return index>=0 && this.matmenus?this.matmenus.find((x,i)=>i==index):null
    }
    reindex(){
      //asign the "index" of the menu item
      this.submenus.forEach(menu=>{
        menu.forEach((x:any)=>{
          if (x.subMenu!=-1)
            x.subMenu=this.menuItems.indexOf(x.action)
        })
      })
    }
    createSubmenus(menu:any[],prefix:string,count:number){
       //add to the array menuItems the "prefix"
       this.menuItems.push(prefix)
       //add to submenu an object to create the submenu
       this.submenus.push(menu.map((x:any,index:number)=>(
         { 
           label:x.label,
           action:x.children===null || x.children===undefined?x.action:prefix+index,
           subMenu:x.children===null || x.children===undefined?-1:0
         }
       )))
       //if has children call the function for each child
       menu.forEach((x:any,index:number)=>{
          if (x.children){
               this.createSubmenus(x.children,prefix+index,count+1)
          }
       })
    }
}

With a .html

<ng-container *ngIf="yet">
  <button mat-button [matMenuTriggerFor]="matmenus.first">
    <ng-content></ng-content>
  </button>
</ng-container>

  <ng-container *ngFor="let menu of submenus">
    <mat-menu>
      <ng-container *ngFor="let item of menu">
      <button mat-menu-item *ngIf="item.subMenu!=-1 && yet"
      [matMenuTriggerFor]="getMenu(item.subMenu)">
           {{item.label}}
      </button>
      <button mat-menu-item *ngIf="item.subMenu==-1" (click)="onClick(item.action)">
        {{item.label}} 
        </button>
      </ng-container>
    </mat-menu>
  </ng-container>

You can see in this stackblitz. See that you only need feed the menuComponent with an array create a function:

action(value:any)
{
  console.log(value)
}

And work as

<menu-component [menu]="menu" (action)="action($event)">
  Animals
</menu-component>
Eliseo
  • 50,109
  • 4
  • 29
  • 67