Angular Material Sidenav迷你变体添加子菜单技术实现咨询
实现Angular Material导航抽屉的折叠/展开子菜单方案
我之前在Angular 5项目里也碰到过完全一样的需求,折腾了一阵后找到了这个兼顾两种状态的可行方案,分享给你:
核心思路
我们需要针对导航抽屉的展开状态和迷你折叠状态分别处理子菜单:
- 展开时:在父菜单项下方显示层级子菜单,用箭头图标控制展开/收起
- 折叠时:点击父菜单项的图标,弹出包含子菜单的popover(用Angular Material的
mat-menu实现)
第一步:实现展开状态下的层级子菜单
首先用mat-nav-list搭建基础导航结构,给带子菜单的项添加点击事件切换子菜单的显示状态,同时在展开状态下显示箭头图标:
模板代码
<mat-sidenav #sidenav mode="side" [opened]="isSidenavOpened" class="sidenav"> <mat-nav-list> <!-- 普通菜单项 --> <a mat-list-item routerLink="/dashboard"> <mat-icon>dashboard</mat-icon> <span *ngIf="isSidenavOpened">Dashboard</span> </a> <!-- 带子菜单的父项 --> <div mat-list-item (click)="toggleSubmenu('reports')" style="cursor: pointer;" [matMenuTriggerFor]="reportsMenu" [matMenuTriggerDisabled]="isSidenavOpened"> <mat-icon>bar_chart</mat-icon> <span *ngIf="isSidenavOpened">Reports</span> <!-- 展开状态下显示箭头 --> <mat-icon *ngIf="isSidenavOpened" class="ml-auto"> {{ expandedSubmenus.reports ? 'expand_less' : 'expand_more' }} </mat-icon> </div> <!-- 嵌套子菜单(仅展开状态且激活时显示) --> <mat-nav-list *ngIf="isSidenavOpened && expandedSubmenus.reports" class="submenu"> <a mat-list-item routerLink="/reports/sales">Sales</a> <a mat-list-item routerLink="/reports/analytics">Analytics</a> </mat-nav-list> <!-- 更多菜单项... --> </mat-nav-list> </mat-sidenav> <!-- 顶部的收起/展开按钮 --> <button mat-icon-button (click)="toggleSidenav()"> <mat-icon>{{ isSidenavOpened ? 'chevron_left' : 'chevron_right' }}</mat-icon> </button>
组件逻辑
import { Component } from '@angular/core'; @Component({ selector: 'app-sidenav', templateUrl: './sidenav.component.html', styleUrls: ['./sidenav.component.scss'] }) export class SidenavComponent { // 控制导航抽屉的展开/折叠状态 isSidenavOpened = true; // 维护各个子菜单的展开状态 expandedSubmenus: { [key: string]: boolean } = { reports: false }; // 切换子菜单的展开/收起 toggleSubmenu(menuKey: string): void { this.expandedSubmenus[menuKey] = !this.expandedSubmenus[menuKey]; } // 切换导航抽屉的展开/折叠 toggleSidenav(): void { this.isSidenavOpened = !this.isSidenavOpened; // 折叠时自动关闭所有子菜单,避免状态混乱 if (!this.isSidenavOpened) { Object.keys(this.expandedSubmenus).forEach(key => { this.expandedSubmenus[key] = false; }); } } }
样式调整
给子菜单添加缩进,让层级更清晰:
.submenu { padding-left: 2rem; // 根据你的图标宽度调整 } .sidenav { width: 250px; &:not([opened]) { width: 60px; } transition: width 0.3s ease; }
第二步:实现折叠状态下的Popover子菜单
这里直接用Angular Material的mat-menu实现popover,并且设置仅在折叠状态下触发:
添加Popover模板
在刚才的代码里,给带子菜单的父项添加mat-menu-trigger,并定义对应的菜单:
<!-- 折叠状态下的Popover菜单 --> <mat-menu #reportsMenu="matMenu" [matMenuPositionX]="'after'"> <button mat-menu-item routerLink="/reports/sales"> <mat-icon>point_of_sale</mat-icon> <span>Sales Reports</span> </button> <button mat-menu-item routerLink="/reports/analytics"> <mat-icon>analytics</mat-icon> <span>Analytics</span> </button> </mat-menu>
关键配置
注意我们给matMenuTriggerDisabled绑定了isSidenavOpened,这样展开状态下会禁用popover触发,避免和子菜单展开逻辑冲突:
[matMenuTriggerDisabled]="isSidenavOpened"
同时设置matMenuPositionX="after"让popover在导航抽屉的右侧弹出,符合用户预期。
第三步:模块导入
确保你的模块里导入了所有需要的Angular Material组件:
import { MatSidenavModule } from '@angular/material/sidenav'; import { MatListModule } from '@angular/material/list'; import { MatIconModule } from '@angular/material/icon'; import { MatMenuModule } from '@angular/material/menu'; import { MatButtonModule } from '@angular/material/button'; @NgModule({ imports: [ // ...其他模块 MatSidenavModule, MatListModule, MatIconModule, MatMenuModule, MatButtonModule ] }) export class AppModule { }
可选优化:封装通用子菜单组件
如果你的项目里有多个带子女菜单的项,建议封装一个通用组件来复用逻辑,比如app-sidenav-submenu:
通用组件模板
<div mat-list-item (click)="isSidenavOpened ? toggleSubmenu() : null" style="cursor: pointer;" [matMenuTriggerFor]="submenu" [matMenuTriggerDisabled]="isSidenavOpened"> <mat-icon>{{ icon }}</mat-icon> <span *ngIf="isSidenavOpened">{{ label }}</span> <mat-icon *ngIf="isSidenavOpened" class="ml-auto"> {{ isExpanded ? 'expand_less' : 'expand_more' }} </mat-icon> </div> <mat-menu #submenu="matMenu" [matMenuPositionX]="'after'"> <button mat-menu-item *ngFor="let item of items" [routerLink]="item.link"> <mat-icon>{{ item.icon }}</mat-icon> <span>{{ item.label }}</span> </button> </mat-menu>
通用组件逻辑
import { Component, Input } from '@angular/core'; @Component({ selector: 'app-sidenav-submenu', templateUrl: './sidenav-submenu.component.html', styleUrls: ['./sidenav-submenu.component.scss'] }) export class SidenavSubmenuComponent { @Input() icon!: string; @Input() label!: string; @Input() items!: Array<{ icon: string; label: string; link: string }>; @Input() isSidenavOpened!: boolean; isExpanded = false; toggleSubmenu(): void { this.isExpanded = !this.isExpanded; } }
主组件中使用
<app-sidenav-submenu icon="bar_chart" label="Reports" [items]="[ { icon: 'point_of_sale', label: 'Sales', link: '/reports/sales' }, { icon: 'analytics', label: 'Analytics', link: '/reports/analytics' } ]" [isSidenavOpened]="isSidenavOpened"> </app-sidenav-submenu>
这样可以大大简化主组件的代码,提升可维护性。
注意事项
- 确保路由配置正确,子菜单的
routerLink能正常跳转 - 样式上可以根据你的设计需求调整导航抽屉宽度、子菜单缩进、popover样式
- 折叠状态下,菜单项的点击区域要足够大,确保用户能轻松触发popover
- 如果需要支持移动端,可以把sidenav的
mode设置为over,并调整响应式逻辑
内容的提问来源于stack exchange,提问作者B. Perez
相关产品推荐
相关产品推荐

