You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 04:07:10