- UI组件
- 前端
【免费下载链接】ng-zorro-antd
Angular UI Component Library based on Ant Design
本指南围绕 ng-zorro-antd(Angular UI 组件库)中的 Menu 导航菜单组件展开,完整覆盖nz-menu、nz-menu-item、nz-submenu、nz-menu-group、nz-menu-divider五个核心指令的 API 与实战用法,并结合组件源码深入剖析模式切换、路由联动、内联折叠、弹出层定位等底层实现原理。读完本文,你将能够在 Angular 项目中独立搭建顶栏、侧边栏及多级嵌套导航菜单,并理解其状态管理机制,为二次定制打好基础。
何时使用
导航菜单对网站而言至关重要,它帮助用户快速从一个站点分区跳转到另一个分区。常见的导航形态包括:
- 顶部导航(Top Navigation):提供网站的所有分类与功能入口,通常采用
horizontal(水平)模式; - 侧边导航(Side Navigation):提供网站的多层级结构,通常采用
inline(内联)或vertical(垂直)模式。
更多与导航相关的布局方案可参考 Layout 布局组件。
快速上手:最小可用示例
NzMenuModule导出全部菜单相关指令,在模块或组件中导入即可使用(参见 menu.module.ts):
<ul nz-menu> <li nz-menu-item>Menu 1</li> <li nz-menu-item>Menu 2</li> <li nz-submenu nzTitle="SubMenu Title"> <ul> <li nz-menu-item>SubMenu Item 1</li> <li nz-menu-item>SubMenu Item 2</li> <li nz-menu-item>SubMenu Item 3</li> </ul> </li> </ul>这是最基础的组合方式:[nz-menu]作用于ul根节点,[nz-menu-item]表示叶子菜单项,[nz-submenu]包裹子菜单并以内部ul承载下级菜单项。默认情况下,菜单以vertical(垂直)模式、light(浅色)主题渲染。
[nz-menu]根指令
[nz-menu]通过NzMenuDirective实现(menu.directive.ts),它负责菜单的模式、主题、折叠状态等全局配置。
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
[nzInlineCollapsed] | 菜单inline模式时是否折叠 | boolean | - |
[nzInlineIndent] | inline模式下每级菜单项的缩进像素值 | number | 24 |
[nzMode] | 菜单类型,支持vertical、horizontal、inline三种模式 | 'vertical' \| 'horizontal' \| 'inline' | 'vertical' |
[nzSelectable] | 是否允许选中菜单项 | boolean | true |
[nzTheme] | 菜单配色主题 | 'light' \| 'dark' | 'light' |
(nzClick) | 点击nz-menu内nz-menu-item时的输出事件 | EventEmitter<NzMenuItemComponent> |
模式(nzMode)与主题(nzTheme)
三种模式与两种主题的类型定义位于 menu.types.ts:
export type NzMenuModeType = 'vertical' | 'horizontal' | 'inline'; export type NzMenuThemeType = 'light' | 'dark';从源码看,nzMode与nzInlineCollapsed会通过combineLatest组合成一个派生状态actualMode:当inline模式且折叠(nzInlineCollapsed = true)时,实际渲染模式会被推导为vertical,随后通过MenuService.setMode()广播给所有子组件(menu.directive.ts):
this.actualMode = inlineCollapsed ? 'vertical' : mode; this.nzMenuService.setMode(this.actualMode);因此“折叠后的 inline 菜单”本质上是垂直模式 + 窄宽度布局,这一设计避免了折叠时子菜单内联展开的尴尬,同时保持弹出式子菜单可用。
点击事件(nzClick)与选中逻辑
nzClick输出源自MenuService.descendantMenuItemClick$流。默认情况下(nzSelectable = true),点击某个菜单项后,菜单会遍历所有nz-menu-item并把选中态收敛到被点击的那一项(menu.directive.ts):
this.nzMenuService.descendantMenuItemClick$.subscribe(menu => { this.nzClick.emit(menu); if (this.nzSelectable && !menu.nzMatchRouter) { this.listOfNzMenuItemDirective.forEach(item => item.setSelectedState(item === menu)); } });值得注意的是:当菜单项启用了路由匹配(nzMatchRouter)时,选中态改由路由驱动,手动点击不再接管,二者不会互相打架。
缩进(nzInlineIndent)
nzInlineIndent默认值为24(menu.directive.ts)。它会通过MenuService.setInlineIndent()下发,nz-menu-item与nz-submenu在inline模式下按层级计算level * inlineIndent的padding-inline-start,实现逐级缩进(见 menu-item.component.ts)。
[nz-menu-item]菜单项
[nz-menu-item]由NzMenuItemComponent实现(menu-item.component.ts),渲染为li.ant-menu-item,内部通过<ng-content>投影用户内容。
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
[nzDisabled] | 是否禁用菜单项 | boolean | false |
[nzSelected] | 菜单项是否选中 | boolean | false |
[nzMatchRouter] | 是否根据routerLink自动设置nzSelected | boolean | false |
[nzMatchRouterExact] | 仅当 URL 与链接完全匹配时才选中,语义同routerLinkActiveOptions | boolean | false |
[nzDanger] | 显示危险样式(红色) | boolean | false |
禁用项的行为
源码中,禁用菜单项在点击时会调用preventDefault()与stopPropagation(),直接阻断事件冒泡与默认行为(menu-item.component.ts):
clickMenuItem(e: MouseEvent): void { if (this.nzDisabled) { e.preventDefault(); e.stopPropagation(); return; } ... }与 Router 的路由联动
nzMatchRouter的实现依赖注入的Router与RouterLink:组件在NavigationEnd事件后检查链接是否激活,并通过router.isActive计算激活状态(menu-item.component.ts):
router.isActive(link.urlTree || '', { paths: this.nzMatchRouterExact ? 'exact' : 'subset', queryParams: this.nzMatchRouterExact ? 'exact' : 'subset', fragment: 'ignored', matrixParams: 'ignored' });即:nzMatchRouterExact = false时采用subset匹配(URL 子集匹配,如/home/users可激活/home);true时采用exact精确匹配。完整可运行示例见 demo/router.ts。
[nz-submenu]子菜单
子菜单是菜单组件中最复杂的部分,同时支持内联展开与弹出层两种渲染形态。
三种设置标题的方式
<!-- 方式一:属性字符串 --> <li nz-submenu nzTitle="SubTitle" nzIcon="appstore"></li> <!-- 方式二:[title] 内容投影 --> <li nz-submenu> <span title> <nz-icon nzType="appstore" /> <span>SubTitle</span> </span> </li> <!-- 方式三:TemplateRef 模板 --> <li nz-submenu [nzTitle]="titleTpl"></li> <ng-template #titleTpl> <nz-icon nzType="appstore" /> <span>SubTitle</span> </ng-template>三种方式分别对应字符串属性、[title]选择器投影与TemplateRef<void>,灵活性覆盖从纯文本到富模板的所有场景。若使用方式一,nzIcon可直接指定标题前的图标类型。
参数表
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
[nzPlacement] | 弹出菜单的位置 | 'bottomLeft' \| 'bottomCenter' \| 'bottomRight' \| 'topLeft' \| 'topCenter' \| 'topRight' | 'bottomLeft' |
[nzOpen] | 子菜单是否展开,支持双向绑定 | boolean | false |
[nzDisabled] | 是否禁用子菜单 | boolean | false |
[nzTitle] | 子菜单标题 | string \| TemplateRef<void> | - |
[nzIcon] | 标题中的图标类型 | string | - |
[nzMenuClassName] | 自定义子菜单容器的 class 名称 | string | - |
[nzTriggerSubMenuAction] | 触发子菜单展开/收起的行为 | 'hover' \| 'click' | 'hover' |
(nzOpenChange) | nzOpen变化回调 | EventEmitter<boolean> | - |
内联模式与弹出模式的分流
从 submenu.component.ts 的模板可以看到,nz-submenu依据当前mode分流渲染:
inline模式:使用nz-submenu-inline-child以内联方式渲染下级ul,配合展开动画;- 其他模式(vertical / horizontal):基于 Angular CDK 的
cdkConnectedOverlay渲染弹出层,弹出宽度在 horizontal 模式下会同步触发源宽度(setTriggerWidth()),保证弹出菜单与标题等宽。
垂直模式下弹出层的位置候选集固定为右侧(rightTop / right / rightBottom)与左侧(leftTop / left / leftBottom)六种(submenu.component.ts),水平模式则使用底部/顶部的水平位置候选集;nzPlacement用于指定首选位置,CDK Overlay 会在空间不足时自动回退到候选位置。
展开状态的管理:150ms 防抖
nzOpen的展开状态由NzSubmenuService(submenu.service.ts)统一管理,其核心逻辑是:
- 子菜单点击某个菜单项后自动收起(非 inline 模式或位于 Dropdown 中时);
- 标题与弹出层区域的
mouseenter / mouseleave状态与子级子菜单展开状态通过combineLatest合并,再经auditTime(150)防抖,避免鼠标在标题与弹出层之间快速移动时子菜单闪烁抖动; - 打开状态会向上传播给父级
NzSubmenuService,因此嵌套多级子菜单可以整链展开。
另外,NzSubmenuService会对模式做一层推导:inline保持不变,垂直模式或处于其他子菜单内部的子菜单统一按vertical处理,水平模式仅作用于顶级子菜单(submenu.service.ts)。
嵌套层级与缩进
NzSubmenuService通过skipSelf注入宿主NzSubmenuService来计算层级level(嵌套子菜单level + 1),inline模式下padding-inline-start按level * inlineIndent递进,多级结构天然缩进正确。
[nz-menu-group]菜单分组
[nz-menu-group]用于给一组菜单项加分组标题,由NzMenuGroupComponent实现(menu-group.component.ts)。
同样支持三种标题设置方式:
<li nz-menu-group nzTitle="SubTitle" nzIcon="appstore"></li> <li nz-menu-group> <span title> <nz-icon nzType="appstore" /> <span>SubTitle</span> </span> </li> <li nz-menu-group [nzTitle]="titleTpl"></li> <ng-template #titleTpl> <nz-icon nzType="appstore" /> <span>SubTitle</span> </ng-template>| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
[nzTitle] | 设置菜单分组标题 | string \| TemplateRef<void> | - |
nzTitle通过nzStringTemplateOutlet统一支持字符串与模板两种输入(menu-group.component.ts),内部还会自动为紧随标题的ul添加ant-menu-item-group-list类。
分组与嵌套子菜单的组合示例可参考 demo/theme.ts:在nz-submenu内嵌套nz-menu-group,每组再包含若干nz-menu-item。
[nz-menu-divider]菜单分割线
[nz-menu-divider]是一条作用于菜单项之间的分割线。需要特别注意的是,官方文档明确说明:它仅用于垂直弹出菜单或 Dropdown 菜单中(即vertical弹出层与nz-dropdown内部),不用于内联菜单。
其实现极简,仅为宿主元素附加ant-dropdown-menu-item-divider类(menu-divider.directive.ts),这也印证了它与 Dropdown 菜单样式的绑定关系:
<li nz-menu-divider></li>主题切换:light 与 dark
菜单主题通过nzTheme在'light'与'dark'之间切换,对应 style/light.less 与 style/dark.less 两套样式入口。
在实际应用中,主题常与状态控件联动。仓库示例 demo/theme.ts 展示了用nz-switch动态切换明暗主题的写法:
<nz-switch [(ngModel)]="theme"> <span checked>Dark</span> <span unchecked>Light</span> </nz-switch> <ul nz-menu nzMode="inline" [nzTheme]="theme ? 'dark' : 'light'"> ... </ul>主题变化通过MenuService.setTheme()广播(menu.service.ts),nz-submenu会订阅theme$同步更新弹出层的主题(submenu.component.ts),保证弹出子菜单与主菜单明暗一致。
折叠模式:inline-collapsed
当菜单为inline模式并希望收缩成仅图标的窄条(常用于 Sider 折叠)时,使用nzInlineCollapsed。仓库示例 demo/inline-collapsed.ts 展示了与nz-sider及开关按钮配合的典型场景。
折叠背后的实现逻辑值得注意(menu.directive.ts):
- 折叠前先记录当前所有已展开的子菜单(
nzOpen为真的子菜单),再全部收起; - 取消折叠时,按记录恢复这些子菜单的展开状态。
这保证了折叠-展开往返过程中,用户此前打开的多级子菜单状态能够被完整还原。
与 Layout 结合:侧边导航
菜单组件与 Layout 布局组件 的nz-sider是黄金搭档。仓库 demo/sider-current.ts 展示了“Sider 折叠 → 菜单同步折叠 → 路由联动高亮”的完整侧边导航方案,配合nz-menu-item的nzMatchRouter可实现刷新后选中态与当前路由保持一致。
模式切换实战
nzMode支持在vertical、horizontal、inline三种模式间动态切换,仓库示例 demo/switch-mode.ts 与 demo/horizontal.ts 分别演示了运行时切换模式与顶部水平导航。源码层面,nzMode变化时会先把所有子菜单强制收起(menu.directive.ts),避免旧模式遗留的展开状态污染新模式布局。
递归菜单与动态数据
当菜单层级不确定时,可采用组件递归渲染。仓库 demo/recursive.ts 给出了一份自递归组件的参考实现:以nz-menu-item为叶子、nz-submenu为分支,通过<ng-template>或递归组件消费菜单树数据,适用于后端动态下发导航结构。
源码架构速览
理解菜单的底层结构有助于二次开发与问题排查:
- menu.directive.ts:根指令,聚合模式/主题/折叠/缩进等全局状态,负责选中态收敛与
nzClick广播; - menu.service.ts:菜单全局状态中心,持有
theme$、mode$、inlineIndent$、isChildSubMenuOpen$等 BehaviorSubject,以及点击事件流descendantMenuItemClick$/childMenuItemClick$; - submenu.service.ts:每个
nz-submenu的局部状态服务,管理自身展开状态、层级与模式推导,并向上传播打开状态; - menu.token.ts:两个内部 DI Token,
NzIsMenuInsideDropdownToken标记菜单是否位于 Dropdown 内,NzMenuServiceLocalToken供嵌套菜单获取本地服务实例; - menu-item.component.ts:菜单项,负责禁用、选中、危险样式与路由联动;
- submenu.component.ts:子菜单,按模式分流内联展开与 CDK Overlay 弹出层。
组件树通过依赖注入天然形成层级:nz-submenu提供局部NzSubmenuService,内部的nz-menu-item与嵌套nz-submenu通过skipSelf向上获取父级服务,从而完成层级计算与打开状态沿链传播;同时NzMenuDirective在提供者中通过MenuServiceFactory区分“根菜单服务”与“外部 Dropdown 注入的菜单服务”(menu.directive.ts),使nz-menu既能在页面中独立使用,也能嵌入nz-dropdown菜单体系。
总结
ng-zorro-antd 的 Menu 组件围绕「根指令 + 服务层 + 层级注入」的架构,提供了vertical/horizontal/inline三种模式、light/dark双主题、内联折叠、路由联动、弹出子菜单与递归渲染等完整能力。掌握本文的 API 表格与源码要点后,你可以快速构建从简单顶栏到复杂侧边多级导航的各类场景;遇到展开/收起异常或层级缩进问题时,也能顺着MenuService与NzSubmenuService的调用链快速定位根因。
- UI组件
- 前端
【免费下载链接】ng-zorro-antd
Angular UI Component Library based on Ant Design
相关推荐
ng-zorro-antd Affix(固钉)组件完全指南:从 API 配置到源码级实现原理
ng zorro antd Affix(固钉)组件完全指南:从 API 配置到源码级实现原理 Affix(固钉)是 ng zorro antd 提供的页面固定组
UI组件前端ng-zorro-antd响应式导航实现:移动端菜单适配
ng zorro antd响应式导航实现:移动端菜单适配 你是否在开发Angular应用时遇到过导航菜单在移动端显示混乱的问题?本文将详细介绍如何使用ng zo
UI组件前端ng-zorro-antd Collapse 折叠面板组件完全指南:从 API 配置到源码实现剖析
ng zorro antd Collapse 折叠面板组件完全指南:从 API 配置到源码实现剖析 导读 本文是 ng zorro antd 中 Collaps
UI组件前端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考