I'm working on a boilerplate project with a configurable menu structure. The menu in it is bound to a recursive data type. I want to solve this via ng-container and ng-template as it looks to me that this is the only way to execute certain steps recursivly.
I have already developed several components using material so I would like to stick with material components as well and not use another menu component.
The problem I have is that if the mat-menu items are defined in a ng-template the menu doesn't get rendered properly. [https://i.imgur.com/WQed1yb.gif]
A menu-trigger-for menu item doesn't seem to be rendered the way it should. It lacks the sub-menu icon, and hovering over it doesn't show up the sub-menu. Instead a click is required. Furthermore the parent menu disappears when the sub-menu is opened which can complicate navigating through the menu.
Besides the mentioned issue the menu by itself does seem to work though.
I tried lazy loading based on a example here: https://www.angularjswiki.com/material/menu/ but that didn't solve the issue.
My HTML template is as follows:
<button mat-button [matMenuTriggerFor]="itemMenu">
{{ startMenuItem.description }}
</button>
<mat-menu #itemMenu="matMenu">
<ng-container *ngFor="let childItem of startMenuItem.children">
<ng-container
*ngTemplateOutlet="recursiveMenuTmpl; context: { $implicit: childItem }"
>
</ng-container>
</ng-container>
</mat-menu>
<ng-template #recursiveMenuTmpl let-parentItem>
<ng-container *ngIf="parentItem.children">
<ng-container
*ngTemplateOutlet="menuItem; context: { $implicit: parentItem }"
></ng-container>
</ng-container>
<ng-container *ngIf="!parentItem.children">
<ng-container
*ngTemplateOutlet="singleItem; context: { $implicit: parentItem }"
></ng-container>
</ng-container>
</ng-template>
<ng-template #menuItem let-rootItem>
<button [matMenuTriggerFor]="itemMenu" mat-menu-item>
{{ rootItem.description }}
</button>
<mat-menu #itemMenu="matMenu">
<ng-container *ngFor="let childItem of rootItem.children">
<ng-container
*ngTemplateOutlet="recursiveMenuTmpl; context: { $implicit: childItem }"
>
</ng-container>
</ng-container>
</mat-menu>
</ng-template>
<ng-template #singleItem let-item>
<button *ngIf="item.route" mat-menu-item [routerLink]="item.uri">
{{ item.description }}
</button>
<button *ngIf="!item.route" mat-menu-item (click)="onOpenUrl(item.uri)">
{{ item.description }}
</button>
</ng-template>
and my component
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-dynamic-menu',
templateUrl: './dynamic-menu.component.html',
styleUrls: ['./dynamic-menu.component.css'],
})
export class DynamicMenuComponent implements OnInit {
public startMenuItem: MenuItem = {
description: 'Menu',
uri: '',
route: false,
children: [
{
description: 'Welcome',
uri: '',
route: false,
children: [],
},
{
description: 'Other',
uri: '',
route: false,
children: [],
},
{
description: 'Sign on/off',
uri: '',
route: false,
children: [
{
description: 'Login',
uri: '',
route: false,
children: [],
},
{
description: 'Logout',
uri: '',
route: false,
children: [],
},
],
},
],
};
constructor() {}
public onOpenUrl(url: string) {
document.location.href = url;
}
ngOnInit(): void {
}
}