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>