Multi level or unlimited level menu are commonly used in admin panel. MatX Angular dashboard has unlimited level menu. MatX is a Free Angular material design admin dashboard template. This side navigation of MatX has unlimited level menu. Let’s take a look at the data structure of this menu.
File on github: navigation.service.ts
iconMenu: IMenuItem[] = [{
name: 'WIZARD',
state: 'forms/wizard',
type: 'link',
icon: 'grain',
},
{
name: 'Multi Level',
type: 'dropDown',
tooltip: 'Multi Level',
icon: 'format_align_center',
sub: [
{ name: 'Level Two', state: 'fake-4' },
{
name: 'Level Two',
type: 'dropDown',
sub: [
{ name: 'Level Three', state: 'fake-2' },
{
name: 'Level Three',
type: 'dropDown',
sub: [
{ name: 'Level Four', state: 'fake-3' },
{
name: 'Level Four',
type: 'dropDown',
sub: [
{ name: 'Level Five', state: 'fake-3' },
{ name: 'Level Five', type: 'link', state: 'fake-3' }
]
}
]
}
]
},
{ name: 'Level Two', state: 'fake-5' }
]
}]
Dropdown menu has type: 'dropDown'
and a sub: []
property, sub contains the child items.
The Logic
Inside the HTML template we have the logic. Menu items array is coming from items
props of the component.
File on github: sidenav.component.ts
@Input('items') public menuItems: any[] = [];
<div class="sidenav-hold" #sidenav>
<ng-container *ngTemplateOutlet="menuTemplate; context: {menuItems: menuItems}"></ng-container>
</div>
<ng-template #menuTemplate let-menuItems="menuItems">
<ul appDropdown class="sidenav">
<li *ngFor="let item of menuItems" appDropdownLink routerLinkActive="open">
<!-- Item -->
<!-- MENU ITEM -->
<div *ngIf="!item.disabled && item.type !== 'separator' && item.type !== 'icon'" class="lvl1">
<a routerLink="/{{item.state}}" appDropdownToggle matRipple
*ngIf="item.type !== 'extLink' && item.type !== 'dropDown'" routerLinkActive="open">
<mat-icon *ngIf="item.icon" class="sidenav-mat-icon">{{item.icon}}</mat-icon>
<mat-icon *ngIf="item.svgIcon" [svgIcon]="item.svgIcon" class="svgIcon"></mat-icon>
<span class="item-name lvl1">{{item.name | translate}}</span>
<span fxFlex></span>
<span class="menuitem-badge mat-bg-{{ badge.color }}" [ngStyle]="{background: badge.color}"
*ngFor="let badge of item.badges">{{ badge.value }}</span>
</a>
<!-- DropDown -->
<a *ngIf="item.type === 'dropDown'" appDropdownToggle matRipple>
<mat-icon *ngIf="item.icon" class="sidenav-mat-icon">{{item.icon}}</mat-icon>
<mat-icon *ngIf="item.svgIcon" [svgIcon]="item.svgIcon" class="svgIcon"></mat-icon>
<span class="item-name lvl1">{{item.name | translate}}</span>
<span fxFlex></span>
<span class="menuitem-badge mat-bg-{{ badge.color }}" [ngStyle]="{background: badge.color}"
*ngFor="let badge of item.badges">{{ badge.value }}</span>
<mat-icon class="menu-caret">keyboard_arrow_right</mat-icon>
</a>
<div *ngIf="item.type === 'dropDown'">
<ng-container *ngTemplateOutlet="menuTemplate; context: {menuItems: item.sub}"></ng-container>
</div>
</div>
</li>
</ul>
</ng-template>
We used ng-template
to render the dropdown menu. We called it #menuTemplate
. We passed the array of menu items to the ng-template
.
We iterate through the array of menu items and render each item. Then we used *ngIf
to conditionally render different types of menu items such as link
, dropdown
, extLink
.
If the item type is dropDown
we render the dropdown toggle item first. Then the child menu items directly underneath of it.
And here is the tricky part. We called ng-template #menuTemplate inside itself. Means it’s calling or referencing itself recursively. And we passed the array of submenu or child items. It renders the child items inside itself.
And again if it finds another dropDown during rending the child items it will again call the ng-template inside child items and render grandchild items.This process will go on as long as it finds the child items.
Expansion panel
This is how the rendering works. Now let’s talk about how expansion panel works. We created few directives. appDropdown
directive which is attached to the ul
tag of menu.appDropdownLink
which is attached to the li
tag of an item
and the appDropdownToggle
toggle which is attached to the a
tag of an item.
These three directive does one job. Add and remove open class to the li tag if you click on it.
CSS
We need a little bit of css to make the drop down Menu actually open and close on click. By default the ul
tag of child menu is set to max-height: 0
, when the open class added by clicking the its parent item we set max-height
to a big number like 1000px
. We assumed the height of the child menu will be always less than 1000px
.You can always increase this number. Here is the CSS code example