- UI组件
- 前端
【免费下载链接】ng-zorro-antd
Angular UI Component Library based on Ant Design
导读
nz-tree是 ng-zorro-antd 中展示多级树形结构(目录、组织架构、分类体系等)的核心组件。当默认的「标题 + 图标」节点渲染方式无法满足业务需求时,nzTreeTemplate允许你完全接管节点的渲染内容,把树变成一个真正的文件管理器式目录视图。本文以官方 demo 目录视图为例,结合仓库源码,讲解nzTreeTemplate的模板上下文(let-node/let-origin)如何工作、如何区分文件夹与文件、如何接入点击、双击与右键菜单,读完即可在业务中复刻并扩展出你自己的目录树。
一、原理解读:nzTreeTemplate是什么
在 components/tree/demo/directory.md 中,官方对目录视图给出了精炼的说明:
使用
nzTreeTemplate实现自定义目录结构,通过let-origin="origin"获得原始数据,let-node获取当前节点状态。
对应到组件 API,components/tree/doc/index.en-US.md 中nzTreeTemplate的定义是:
| Property | Description | Type | Default |
|---|---|---|---|
[nzTreeTemplate] | Custom Nodes(自定义节点模板) | TemplateRef<{ $implicit: NzTreeNode; origin: NzTreeNodeOptions }> | - |
也就是说,nzTreeTemplate接收一个TemplateRef,该模板的上下文(context)固定包含两个字段:
$implicit(即let-node解构出来的值):当前节点的NzTreeNode实例,封装了isLeaf、isExpanded、title、key、isSelected、isChecked、isLoading等运行状态;origin(即let-origin="origin"解构出来的值):当前节点对应的NzTreeNodeOptions原始数据,也就是你传给[nzData]的那份用户自定义数据,可以携带任意自定义字段。
这一设计让「模板渲染层」和「状态管理层」职责分离:模板中既可以通过node读取/切换运行状态(如node.isExpanded),又可以通过origin拿到业务原始数据(如作者、创建时间、文件大小等)。
源码佐证:模板上下文是如何注入的
从源码可以确认模板上下文的确切形状。在 tree-node-title.component.ts 中,自定义模板通过NgTemplateOutlet渲染,并显式注入了两个字段:
<ng-template [ngTemplateOutlet]="treeTemplate" [ngTemplateOutletContext]="{ $implicit: context, origin: context.origin }" />其中context就是当前节点的NzTreeNode实例。而nzTreeTemplate输入的类型签名同样在 tree.component.ts 与 tree-node.component.ts 中被声明为:
TemplateRef<{ $implicit: NzTreeNode; origin: NzTreeNodeOptions }>因此模板中let-node拿到的是NzTreeNode,let-origin="origin"拿到的是NzTreeNodeOptions(你的原始数据),两者在渲染时一一对应。
注意:如果你选择把模板作为
<ng-template #nzTreeTemplate>放在nz-tree内部,组件还会通过@ContentChild('nzTreeTemplate')自动捕获它(见 tree.component.ts),内部渲染时采用nzTreeTemplate || nzTreeTemplateChild的优先级(见 tree.component.ts)。两种写法效果等价,后者更简洁。
二、完整实现:一个可运行的文件目录树
官方 demo 的完整源码位于 components/tree/demo/directory.ts,它同时使用了模板引用变量(#nzTreeTemplate)和内联模板两种形态。以下按「数据 → 模板 → 交互 → 样式」四步拆解。
2.1 准备数据:携带自定义字段的NzTreeNodeOptions
目录树的数据结构依然是标准的NzTreeNodeOptions,但关键在于NzTreeNodeOptions是「可索引类型」([key: string]: any),允许你随意追加业务字段。官方 demo 中为每个节点追加了author字段,用于在节点描述里展示:
readonly nodes = [ { title: 'parent 0', key: '100', author: 'NG ZORRO', expanded: true, children: [ { title: 'leaf 0-0', key: '1000', author: 'NG ZORRO', isLeaf: true }, { title: 'leaf 0-1', key: '1001', author: 'NG ZORRO', isLeaf: true } ] }, { title: 'parent 1', key: '101', author: 'NG ZORRO', children: [ { title: 'leaf 1-0', key: '1010', author: 'NG ZORRO', isLeaf: true }, { title: 'leaf 1-1', key: '1011', author: 'NG ZORRO', isLeaf: true } ] } ];要点:
key必须全局唯一,它是树的稳定标识;- 叶子节点显式声明
isLeaf: true(该属性默认值为false,见 components/tree/doc/index.en-US.md 的NzTreeNodeOptions表); expanded: true让根目录默认展开;- 自定义字段
author在模板中通过let-origin读取,这正是自定义目录视图能够展示「created by XXX」这类业务信息的基础。
2.2 编写自定义模板:用node与origin区分目录和文件
模板是整个目录视图的核心。官方 demo 在nz-tree内声明了一个模板引用变量,然后通过属性绑定[nzTreeTemplate]传入:
<nz-tree nzBlockNode [nzData]="nodes" (nzClick)="activeNode($event)" (nzDblClick)="openFolder($event)" [nzTreeTemplate]="nzTreeTemplate" /> <ng-template #nzTreeTemplate let-node let-origin="origin"> <span class="custom-node"> @if (!node.isLeaf) { <span (contextmenu)="contextMenu($event, menu)"> <nz-icon [nzType]="node.isExpanded ? 'folder-open' : 'folder'" (click)="openFolder(node)" /> <span class="folder-name">{{ node.title }}</span> <span class="folder-desc">created by {{ origin.author | lowercase }}</span> </span> } @else { <span (contextmenu)="contextMenu($event, menu)"> <nz-icon nzType="file" /> <span class="file-name">{{ node.title }}</span> <span class="file-desc">modified by {{ origin.author | lowercase }}</span> </span> } </span> </ng-template>逐行解读:
let-node解构$implicit,即当前NzTreeNode实例,模板中用node.isLeaf判断「是文件夹还是文件」,用node.isExpanded切换文件夹图标(folder-open/folder),用node.title渲染节点名称;let-origin="origin"解构原始数据,模板中用origin.author渲染业务描述,并通过 Angular 内置LowerCasePipe转小写;@if/@else是 Angular 17+ 的控制流语法,对文件夹与文件分别渲染不同图标与文案;nzBlockNode让节点占满整行(对应 API 表中的nzBlockNode属性,默认false),这是目录树「整行可点击」观感的前提;- 图标使用
nz-icon(来自NzIconModule),folder/folder-open/file均为内置图标名。
2.3 事件交互:点击选中、双击展开/折叠、右键菜单
目录视图的交互在组件类中定义(见 components/tree/demo/directory.ts):
export class NzDemoTreeDirectoryComponent { private readonly nzContextMenuService = inject(NzContextMenuService); activatedNode?: NzTreeNode; openFolder(data: NzTreeNode | NzFormatEmitEvent): void { // do something if u want if (data instanceof NzTreeNode) { data.isExpanded = !data.isExpanded; } else { const node = data.node; if (node) { node.isExpanded = !node.isExpanded; } } } activeNode(data: NzFormatEmitEvent): void { this.activatedNode = data.node!; } contextMenu($event: MouseEvent, menu: NzDropdownMenuComponent): void { this.nzContextMenuService.create($event, menu); } selectDropdown(): void { // do something } }三个交互各司其职:
- 单击选中:
(nzClick)="activeNode($event)",事件载荷是NzFormatEmitEvent,取其node字段即可获得当前NzTreeNode并保存为activatedNode,后续操作(如工具栏按钮)可以引用它; - 双击打开/关闭文件夹:
(nzDblClick)="openFolder($event)",openFolder同时兼容两种入参——直接传入NzTreeNode(模板中图标(click)="openFolder(node)"走这条路),或传入NzFormatEmitEvent(双击事件走这条路),核心都是翻转node.isExpanded; - 右键菜单:
(contextmenu)="contextMenu($event, menu)"配合NzContextMenuService.create($event, menu)弹出nz-dropdown-menu,菜单项定义在模板中:
<nz-dropdown-menu #menu="nzDropdownMenu"> <ul nz-menu> <li nz-menu-item (click)="selectDropdown()">Action 1</li> <li nz-menu-item (click)="selectDropdown()">Action 2</li> </ul> </nz-dropdown-menu>注意(nzClick)、(nzDblClick)等事件在 tree.component.ts 中均声明为EventEmitter<NzFormatEmitEvent>,而NzFormatEmitEvent由nz-tree内部的eventTriggerChanged统一派发(见 tree.component.ts),其结构包含eventName、node、event、selectedKeys、keys等字段,具体字段说明见 components/tree/doc/index.en-US.md 的NzFormatEmitEvent表。
2.4 样式:让节点呈现「资源管理器」质感
demo 附带了一段组件样式(见 components/tree/demo/directory.ts),核心作用是把「文件夹名 + 描述标签」组织成一行,并让描述标签呈现底色胶囊效果:
nz-tree { overflow: hidden; margin: 0 -24px; padding: 0 24px; } .custom-node { cursor: pointer; line-height: 24px; display: inline-block; } .custom-node, .file-name, .folder-name { margin-inline-start: 4px; } .file-desc, .folder-desc { padding: 0 8px; display: inline-block; background: #87ceff; color: #ffffff; position: relative; inset-inline-start: 12px; }其中的margin-inline-start、inset-inline-start是逻辑属性,可保证在 RTL(从右到左)布局下依然正确;#87ceff天蓝色背景配合白色文字,就是官方文档截图中的目录描述标签效果。实际业务中可以换成任意品牌色或语义色。
三、NzTreeNode与NzTreeNodeOptions:两套数据的配合关系
要写出健壮的自定义模板,需要清楚两套对象的边界:
NzTreeNodeOptions(原始数据,模板中的origin)
- 由用户传入
[nzData],是渲染前你的业务数据形态; - 内置字段:
title(默认'---')、key、icon、children、isLeaf、checked、selected、expanded、selectable、disabled、disableCheckbox; [key: string]: any可索引类型允许追加任意自定义字段,官方文档明确说明「NzTreeNodeOptionsaccepts your custom properties,useNzTreeNode.originto get them」。
NzTreeNode(运行实例,模板中的node)
- 由组件内部基于
NzTreeNodeOptions构建,模板中应通过它读取运行状态:isLeaf、isExpanded、isSelected、isChecked、isHalfChecked、isDisabled、isLoading、isMatched等; - 可通过
node.origin反向拿到原始数据; - 也暴露了操作方法:
setExpanded、setSyncChecked、addChildren、clearChildren、remove等,完整列表见 components/tree/doc/index.en-US.md 的NzTreeNode表。
在目录视图里最典型的用法就是:用node驱动交互与状态展示(图标、展开态),用origin展示业务数据(作者、描述)。两者各自负责自己擅长的领域,模板不会因为状态字段的变化而丢失业务数据。
四、模板上下文在渲染链路上的完整传递
理解整条传递链路有助于排查「模板不生效」类问题(可对照 tree.component.ts 与 tree-node.component.ts):
nz-tree组件接收[nzTreeTemplate]输入,或者通过@ContentChild('nzTreeTemplate')捕获内容子模板(tree.component.ts);- 在虚拟滚动与普通渲染两条分支中,模板都被下发给每个
nz-tree-node([nzTreeTemplate]="nzTreeTemplate || nzTreeTemplateChild",见 tree.component.ts 与 tree.component.ts); nz-tree-node把模板连同[context]="nzTreeNode"一起传给nz-tree-node-title(tree-node.component.ts);nz-tree-node-title最终通过NgTemplateOutlet注入{ $implicit: context, origin: context.origin }并渲染(tree-node-title.component.ts)。
当treeTemplate为空时,nz-tree-node-title会回退到默认渲染:图标(配合nzShowIcon)+nzHighlight高亮后的标题(tree-node-title.component.ts)。也就是说,自定义模板是「全量替换」标题区域,树的结构(缩进、展开箭头、连接线、复选框、拖拽指示器)依然由组件内置渲染,你无需担心破坏树的骨架。
五、实战注意点与扩展建议
5.1nzData必须先行
官方 API 文档有一句重要提示(见 components/tree/doc/index.en-US.md):根据当前数据结构设计,需要先确保nzData已设置,否则其他属性(nzExpandAll、nzExpandedKeys、nzCheckedKeys、nzSelectedKeys、nzSearchValue)不会生效;异步接口返回数据后,需要重新赋值这些属性以触发重新渲染。目录树若涉及异步加载目录,请遵守这一顺序。
5.2 自定义模板内部的事件冒泡与$event使用
模板中直接绑定的原生事件(如click、contextmenu)与树组件事件是两个层级:node上的(click)会触发openFolder(node)(展开/折叠),而nz-tree的(nzClick)接收的是组件格式化后的NzFormatEmitEvent。两者可并存:前者用于局部交互,后者用于记录选中状态。右键菜单事件要记得传入$event与菜单引用,NzContextMenuService会在鼠标位置弹出菜单。
5.3 数据量大时的性能考量
如果目录树节点非常多,可以开启虚拟滚动:设置nzVirtualHeight(视口高度字符串,如'300px')、nzVirtualItemSize(默认28)、nzVirtualMinBufferPx(默认28)、nzVirtualMaxBufferPx(默认500),组件会切换到cdk-virtual-scroll-viewport渲染路径(见 tree.component.ts),自定义模板同样生效,因为两条渲染分支都传入了nzTreeTemplate。
5.4 从 demo 到业务:可扩展方向
基于官方 demo(components/tree/demo/directory.ts)可以低成本扩展出完整业务目录树:
- 在
NzTreeNodeOptions中增加fileSize、modifiedAt、permission等字段,通过let-origin渲染文件属性列; - 为不同文件类型映射不同图标(如
file-text、file-image、file-zip); - 把
selectDropdown()改为真实的删除、重命名、新建文件夹逻辑,操作对象用activatedNode(单击选中节点)或右键所在节点; - 接入
nzSearchValue与nzSearchFunc实现目录搜索,命中节点会被自动高亮并展开父级(搜索匹配逻辑见 tree.component.ts)。
总结
nzTreeTemplate是 ng-zorro-antd Tree 组件定制能力的核心入口。通过let-node与let-origin两个上下文变量,你可以把默认的纯文本节点替换为「文件夹 / 文件」形态的完整目录视图,同时保有树组件内置的缩进、展开、勾选、拖拽与虚拟滚动能力。官方目录 demo(components/tree/demo/directory.md 与 directory.ts)是一个高度可复用的起点:数据层用NzTreeNodeOptions携带业务字段,渲染层用node驱动状态、origin展示数据,交互层用nzClick/nzDblClick/NzContextMenuService打通选中、展开与右键操作。理解了这条链路,你就能在任意业务场景中构建出符合产品形态的自定义树形交互。
- UI组件
- 前端
【免费下载链接】ng-zorro-antd
Angular UI Component Library based on Ant Design
相关推荐
ng-zorro-antd Steps 自定义点状步骤条(nzProgressDot 模板)实战指南
ng zorro antd Steps 自定义点状步骤条(nzProgressDot 模板)实战指南 点状步骤条(Progress Dot)是 ng zorro
UI组件前端ng-zorro-antd Graph 自定义节点样式实战:从 foreignObject 模板到交互控制
ng zorro antd Graph 自定义节点样式实战:从 foreignObject 模板到交互控制 Graph(流程图)是 ng zorro antd
UI组件前端gh-aw BYOK完全指南:自带密钥驱动多模型AI引擎的7个实战要点
gh aw BYOK完全指南:自带密钥驱动多模型AI引擎的7个实战要点 gh aw (GitHub Agentic Workflows)是 GitHub 官方的
UI组件前端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考