Reputation: 6083
In documentation of Angular Material there is cool feature. Menu with subcategory which can be expanded/collapsed.
Is it possible to create it using some component? Or I have to do it myself from scratch? Or maybe you can suggest me some package that save my time.
Upvotes: 7
Views: 23636
Reputation: 6883
You can combine mat-sidenav
with mat-tree
to achieve this. This can be done without any 'hacky' fixes, which are needed if you try to use mat-accordion
instead.
I created this Stackblitz example that shows the result.
HTML:
<mat-nav-list>
<mat-tree [dataSource]="menuItems" [treeControl]="menuTreeControl">
<mat-tree-node *matTreeNodeDef="let menuItem">
<a
[routerLink]="menuItem.link"
mat-list-item
routerLinkActive="mdc-list-item--activated"
>
{{ menuItem.name }}
</a>
</mat-tree-node>
<mat-nested-tree-node *matTreeNodeDef="let menuItem; when: hasChild">
<a mat-list-item matTreeNodeToggle>
<span matListItemTitle
>{{ menuItem.name }}
<mat-icon>
{{
menuTreeControl.isExpanded(menuItem)
? "expand_more"
: "expand_less"
}}
</mat-icon>
</span>
</a>
<div *ngIf="menuTreeControl.isExpanded(menuItem)" role="group">
<ng-container matTreeNodeOutlet></ng-container>
</div>
</mat-nested-tree-node>
</mat-tree>
</mat-nav-list>
SCSS:
mat-nav-list {
div[role="group"] {
padding-left: 20px;
}
mat-icon {
position: absolute;
right: 20px;
}
}
Component:
export class NavMenuComponent {
previewEnabled = Cookies.get("previewEnabled") === "true";
title$: Observable<string>;
menuTreeControl = new NestedTreeControl<MenuItem>(
(menuItem) => menuItem.children
);
menuItems: MenuItem[] = [
{ type: "link", link: "link-1", name: "Item 1"},
{
type: "section",
name: "Parent Item",
children: [
{ type: "link", link: "link-2", name: "Item 2" },
{ type: "link", link: "link-3", name: "Item 3" },
],
},
];
constructor(
private router: Router,
) {
router.events
.pipe(filter((event) => event instanceof NavigationEnd))
.subscribe(() => this.openActiveRouteParentMenuItem());
}
openActiveRouteParentMenuItem() {
_.forEach(this.menuItems, (parentMenuItem) => {
const childMenuItems = this.menuTreeControl.getChildren(
parentMenuItem
) as MenuItem[];
_.forEach(childMenuItems, (childMenuItem) => {
if (
this.router.isActive(childMenuItem.link as string, {
paths: "subset",
queryParams: "subset",
fragment: "ignored",
matrixParams: "ignored",
})
) {
this.menuTreeControl.expand(parentMenuItem);
}
});
});
}
hasChild = (_: number, menuItem: MenuItem) => !!menuItem.children?.length;
}
interface MenuItem {
type: "link" | "section";
name: string;
link?: string;
children?: MenuItem[];
}
Upvotes: 1
Reputation: 1724
Place mat-expansion-panel
's in a mat-accordion
and mat-nav-list
's in the expansion panels. Then create a global style for the new navigation accordion.
<mat-accordion class="app-nav-accordion">
<mat-expansion-panel class="mat-elevation-z0">
<mat-expansion-panel-header>
<mat-panel-title>Section Two</mat-panel-title>
</mat-expansion-panel-header>
<mat-nav-list>
<a mat-list-item>Item Three</a>
<a mat-list-item>Item Four</a>
</mat-nav-list>
</mat-expansion-panel>
</mat-accordion>
.app-nav-accordion {
.mat-expansion-panel {
border-radius: 0px !important;
box-shadow: none !important;
&.mat-expansion-panel-spacing {
margin: 0px;
}
.mat-expansion-panel-body {
padding: 0px;
}
.mat-expansion-panel-header {
height: 40px;
padding: 0px 24px 0px 16px;
}
.mat-expansion-panel-header-title {
color: rgba(0,0,0,0.54);
font-size: 14px;
font-weight: 500;
}
}
}
Upvotes: 4
Reputation: 81
I tried to achieve this myself with a side-nav. Maybe it can save you some time.
The nav-items follows a simple interface:
interface NavItem {
displayName: string;
disabled?: boolean;
iconName: string;
route?: string;
children?: NavItem[];}
And then in the component.html the main idea is to add the nav-items recursively (*ngFor). Use mat-accordions for each category and then use another *ngFor to get the children:
<mat-nav-list>
<span *ngFor="let item of menu">
<span *ngIf="item.children && item.children.length > 0">
<mat-accordion>
<mat-expansion-panel>
<mat-expansion-panel-header>
<mat-panel-title>
<!-- Cabeceras del submenu -->
<div fxLayout="row" fxLayoutAlign="space-between center" >
<mat-icon>{{item.iconName}}</mat-icon>
{{item.displayName}}
</div>
</mat-panel-title>
</mat-expansion-panel-header>
<span *ngFor="let child of item.children">
<mat-list-item routerLink="[child.route]">
<!-- Entradas de cada submenú -->
<div fxLayout="row" fxLayoutAlign="space-between center" >
<mat-icon>{{child.iconName}}</mat-icon>
{{child.displayName}}
</div>
</mat-list-item>
</span>
</mat-expansion-panel>
</mat-accordion>
</span>
<span *ngIf="!item.children || item.children.length === 0">
<mat-list-item routerLink="[item.route]">
<!-- Entradas principales -->
<div fxLayout="row" fxLayoutAlign="space-between center">
<mat-icon>{{item.iconName}}</mat-icon>
{{item.displayName}}
</div>
</mat-list-item>
</span>
</span>
Have a look: https://stackblitz.com/edit/angular-side-nav-dynamic-expansive-menu
Upvotes: 8
Reputation: 7723
Using the mat-expansion-panel would be a good start. The default example uses accordion to have only one expanded panel, you can find an example of how to expand multiple panels at the same time here :
1- You should remove the mat-accordion to enable multiple expanded panels.
2- Use the expanded parameter to change multiple states at the same time.
Upvotes: 0