- UI组件
- 前端
【免费下载链接】ng-zorro-antd
Angular UI Component Library based on Ant Design
导读
菜单(Menu)是导航类页面的核心组件,而"动态切换菜单类型"是后台管理系统中最常见的一类交互需求——同一套菜单结构,需要跟随用户偏好或设备宽度在inline(内联)与vertical(垂直弹出)两种模式间即时切换,甚至同步切换light/dark主题。本篇基于 ng-zorro-antd 官方演示switch-mode展开,先给出可直接复制的完整示例代码,再深入 menu.directive.ts 与 submenu.component.ts 源码,讲清模式切换背后"子菜单渲染形态如何改变、打开状态如何重置"的底层机制。读完你可以独立实现一个可实时切换模式与主题的导航菜单。
演示场景:展示动态切换模式
该演示的核心意图只有一句话:展示动态切换模式(between 'inline' and 'vertical')。它通过两个nz-switch开关分别控制:
- 菜单类型(Mode):
vertical与inline之间切换; - 菜单主题(Theme):
dark与light之间切换。
切换是即时的,无需重新渲染页面,菜单内的三级子菜单结构保持不变。这正是 switch-mode.ts 所呈现的效果,也是它区别于其他 demo(如固定模式的 vertical、inline)的关键:模式作为可变的响应式状态绑定到[nzMode]。
完整可运行示例:代码拆解
官方演示的完整代码如下(见 switch-mode.ts):
import { Component, signal } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { NzDividerModule } from 'ng-zorro-antd/divider'; import { NzMenuModule } from 'ng-zorro-antd/menu'; import { NzSwitchModule } from 'ng-zorro-antd/switch'; @Component({ selector: 'nz-demo-menu-switch-mode', imports: [FormsModule, NzDividerModule, NzMenuModule, NzSwitchModule], template: ` <nz-switch [(ngModel)]="mode" /> Change Mode <nz-divider nzType="vertical" /> <nz-switch [(ngModel)]="dark" /> Change Theme <br /> <br /> <ul nz-menu [nzMode]="mode() ? 'vertical' : 'inline'" [nzTheme]="dark() ? 'dark' : 'light'"> <li nz-submenu nzTitle="Navigation One" nzIcon="mail"> <ul> <li nz-menu-group nzTitle="Item 1"> <ul> <li nz-menu-item>Option 1</li> <li nz-menu-item>Option 2</li> </ul> </li> <li nz-menu-group nzTitle="Item 2"> <ul> <li nz-menu-item>Option 3</li> <li nz-menu-item>Option 4</li> </ul> </li> </ul> </li> <li nz-submenu nzTitle="Navigation Two" nzIcon="appstore"> <ul> <li nz-menu-item>Option 5</li> <li nz-menu-item>Option 6</li> <li nz-submenu nzTitle="Submenu"> <ul> <li nz-menu-item>Option 7</li> <li nz-menu-item>Option 8</li> </ul> </li> </ul> </li> <li nz-submenu nzTitle="Navigation Three" nzIcon="setting"> <ul> <li nz-menu-item>Option 9</li> <li nz-menu-item>Option 10</li> <li nz-menu-item>Option 11</li> </ul> </li> </ul> `, styles: ` [nz-menu] { width: 240px; } ` }) export class NzDemoMenuSwitchModeComponent { readonly mode = signal(false); readonly dark = signal(false); }关键点逐项说明
- 模块依赖:
NzMenuModule提供nz-menu指令及子组件;NzSwitchModule提供开关;NzDividerModule用于分隔两个开关;FormsModule是[(ngModel)]双向绑定所必需。 - 状态管理:组件用 Angular 的
signal声明mode与dark两个布尔状态,默认均为false,即初始呈现inline模式 +light主题。 - 双向数据流:
<nz-switch [(ngModel)]="mode" />把开关值与信号绑定,模板中通过mode() ? 'vertical' : 'inline'三元表达式实时计算nzMode取值。开关一拨,菜单类型即刻变化。 - 菜单结构:三个
nz-submenu顶级子菜单(Navigation One / Two / Three),内部混合使用nz-menu-group分组、nz-menu-item菜单项,以及嵌套的二级nz-submenu("Submenu"),覆盖了内联模式下最常见的多级场景。样式上给[nz-menu]固定了240px宽度,便于观察内联与垂直弹出两种形态的差异。
模式与主题相关的核心 API
模式切换本质上是在操作[nz-menu]指令的两个输入属性。下表整理自菜单官方文档 doc/index.en-US.md,并对照 menu.directive.ts 源码中的实际默认值:
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
[nzMode] | 菜单类型,支持vertical、horizontal、inline | 'vertical' \| 'horizontal' \| 'inline' | 'vertical' |
[nzTheme] | 菜单颜色主题 | 'light' \| 'dark' | 'light' |
[nzInlineCollapsed] | inline模式下是否折叠 | boolean | false |
[nzInlineIndent] | inline模式下每一级菜单项的缩进像素值 | number | 24 |
[nzSelectable] | 是否允许选中菜单项 | boolean | true |
(nzClick) | 点击nz-menu-item时的输出事件 | EventEmitter<NzMenuItemComponent> | — |
其中nzMode与nzTheme的取值类型在 menu.types.ts 中有明确定义:
export type NzMenuModeType = 'vertical' | 'horizontal' | 'inline'; export type NzMenuThemeType = 'light' | 'dark'; export type NzSubmenuTrigger = 'hover' | 'click';nz-submenu子组件还支持nzTriggerSubMenuAction('hover' | 'click',默认'hover')控制展开触发方式、nzPlacement控制弹出位置(默认'bottomLeft')、nzOpen(默认false)控制受控展开状态等,详见 submenu.component.ts。
源码视角:模式切换时内部发生了什么
动态切换不是简单的样式替换,而是组件内部状态机的一次联动。沿着 menu.directive.ts 的代码可以看清整条链路。
1. 输入变化被转发到响应式流
NzMenuDirective持有两个BehaviorSubject:mode$与inlineCollapsed$。ngOnChanges中一旦检测到nzMode变化,就会把新值推入mode$,并主动将所有子菜单的打开状态置为关闭:
if (nzMode) { this.mode$.next(this.nzMode); if (!nzMode.isFirstChange() && this.listOfNzSubMenuComponent) { this.listOfNzSubMenuComponent.forEach(submenu => submenu.setOpenStateWithoutDebounce(false)); } }这段代码解释了演示中一个容易被忽略的细节:切换模式的瞬间,之前展开的子菜单会全部收起。因为inline与vertical的子菜单展开形态完全不同(前者是页面内展开,后者是浮层弹出),保留旧打开状态会造成视觉错乱,所以框架选择在模式切换时重置。
nzTheme变化则通过setTheme转发给服务层:
if (nzTheme) { this.nzMenuService.setTheme(this.nzTheme); }2. 模式与折叠共同决定"实际模式"
menu.directive.ts 在ngOnInit中用combineLatest合并inlineCollapsed$与mode$,计算出实际生效的模式:
combineLatest([this.inlineCollapsed$, this.mode$]) .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(([inlineCollapsed, mode]) => { this.actualMode = inlineCollapsed ? 'vertical' : mode; this.nzMenuService.setMode(this.actualMode); this.cdr.markForCheck(); });也就是说,即便nzMode设为inline,一旦nzInlineCollapsed为true,实际模式也会降级为vertical(折叠态用弹出浮层展示子菜单)。这为后续扩展"折叠"交互提供了伏笔。
3. 服务层广播模式与主题
MenuService 是菜单内部各组件共享的中枢,通过BehaviorSubject广播状态:mode$、theme$、inlineIndent$、isChildSubMenuOpen$等,并提供setMode/setTheme/setInlineIndent方法。菜单项与子菜单都订阅这些流来响应变化,这正是"一个开关、全局联动"得以实现的原因。
4. 子菜单按模式选择渲染形态
这是模式切换最直观的源码体现。在 submenu.component.ts 的模板中,@if (mode === 'inline')分支渲染内联子菜单(nz-submenu-inline-child,通过折叠动画在页面内展开);否则渲染非内联子菜单(nz-submenu-none-inline-child,配合cdkConnectedOverlay以浮层弹出):
@if (mode === 'inline') { <div nz-submenu-inline-child [open]="nzOpen" ...> <ng-template [ngTemplateOutlet]="subMenuTemplate" /> </div> } @else { <ng-template cdkConnectedOverlay [cdkConnectedOverlayPositions]="overlayPositions" [cdkConnectedOverlayOrigin]="origin" [cdkConnectedOverlayOpen]="nzOpen" ... > ... </ng-template> }子菜单组件在ngOnInit中订阅mode$,并据模式切换浮层的定位列表:
if (mode === 'horizontal') { this.overlayPositions = [POSITION_MAP[this.nzPlacement], ...listOfHorizontalPositions]; } else if (mode === 'vertical') { this.overlayPositions = listOfVerticalPositions; }vertical模式:弹出面板出现在子菜单项右侧或左侧(rightTop / right / rightBottom / leftTop / left / leftBottom);horizontal模式:弹出面板出现在下方(bottomLeft / bottomRight / topRight / topLeft)。
动画也随之区分:在 submenu-non-inline-child.component.ts 中,vertical使用ant-zoom-big缩放动画,horizontal使用slide-up上滑动画。
5. 菜单项与层级缩进
inline模式最显著的视觉特征是多级缩进。在 menu-item.component.ts 中,菜单项订阅mode$与inlineIndent$,仅在inline模式下按层级计算缩进:
this.inlinePaddingLeft = mode === 'inline' ? this.level * inlineIndent : null;对应 submenu.component.ts 中同样的计算逻辑(inlinePaddingLeft = mode === 'inline' ? this.level * inlineIndent : null)。默认nzInlineIndent = 24,即每深入一级缩进 24px。
三种模式的适用场景与子菜单行为对比
| 模式 | 子菜单形态 | 典型场景 | 演示参考 |
|---|---|---|---|
inline | 页面内折叠展开(nz-submenu-inline-child+ 折叠动画) | 侧边导航、多级目录树 | inline demo |
vertical | 右侧/左侧浮层弹出(zoom-big 动画) | 下拉式导航、抽屉内菜单 | vertical demo |
horizontal | 下方浮层弹出(slide-up 动画) | 顶部导航栏 | horizontal demo |
此外 inline-collapsed.ts 演示了inline模式下的折叠态:折叠时actualMode降级为vertical,子菜单改以浮层展示——这与上文combineLatest的逻辑完全对应。
扩展实战:把模式切换接入布局骨架
switch-mode演示的是一块独立菜单的切换。在实际后台系统中,更常见的做法是把mode状态提升到根组件,与 Layout 的Sider联动:侧边栏展开时菜单为inline,收起时切换为vertical弹出。也可以参考 sider-current.ts 的"侧边栏中选中项自动高亮"写法,结合nzMatchRouter让菜单跟随路由高亮;再配合 theme.ts 将nzTheme状态与全局主题配置统一管理。
实现时需注意两点:
- 若菜单放在侧边栏中,应让
nz-menu的宽度与Sider宽度保持一致,避免vertical弹出浮层定位偏差; - 模式切换会重置所有子菜单的展开状态(源码已保证),因此不要依赖"切换前后子菜单保持展开"的行为,必要时在切换后通过
nzOpen受控属性主动恢复。
小结
"切换菜单类型"演示虽小,却是理解 ng-zorro-antd 菜单架构的最佳入口:一个nzMode输入,背后串联起NzMenuDirective的响应式流、MenuService的状态广播、子菜单按模式切换渲染形态(内联展开 vs CDK Overlay 浮层)、定位与动画的联动,以及菜单项层级缩进的重算。掌握这条链路后,无论是要做顶部导航、侧边导航还是折叠交互,都能基于同一套菜单结构快速落地。
- UI组件
- 前端
【免费下载链接】ng-zorro-antd
Angular UI Component Library based on Ant Design
相关推荐
Ant Design Menu 动态切换模式实战:在 inline 与 vertical 之间实时切换导航布局
Ant Design Menu 动态切换模式实战:在 inline 与 vertical 之间实时切换导航布局 导读 本文基于 ant design 仓库 Me
前端UI组件设计系统ng-zorro-antd FloatButton 形状详解:用 nzShape 在圆形与方形之间切换
ng zorro antd FloatButton 形状详解:用 nzShape 在圆形与方形之间切换 导读 nz float button (浮动按钮)是 n
UI组件前端antd ColorPicker 渐变色模式实战:用 `mode` 在单色与线性渐变之间自由切换
antd ColorPicker 渐变色模式实战:用 mode 在单色与线性渐变之间自由切换 导读 ColorPicker 组件默认只能选取单一颜色,而通过 5
前端UI组件设计系统
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考