如何向已有的Angular Mat-Tree节点添加子节点并实现展开动态加载层级
Angular Mat-tree 动态加载子目录与新增子项实现方案
前置准备
首先明确两类节点定义,你可根据业务调整字段:
// 嵌套型原始目录节点,对应后台返回的目录结构 interface DirectoryItemNode { id: string; // 建议加唯一标识方便节点匹配 name: string; children?: DirectoryItemNode[]; expandable: boolean; isLoaded?: boolean; // 标记子级是否已加载,避免重复请求 } // 扁平型渲染节点,用于Mat-tree渲染 interface DirectoryItemFlatNode { id: string; name: string; level: number; expandable: boolean; isLoaded?: boolean; }
你现有基础代码可保留,补充节点转换方法即可:
// 补充转换方法,把嵌套节点转为扁平节点 private _transformer = (node: DirectoryItemNode, level: number) => { return { id: node.id, name: node.name, level: level, expandable: node.expandable, isLoaded: node.isLoaded ?? false }; }; treeControl = new FlatTreeControl<DirectoryItemFlatNode>( node => node.level, node => node.expandable, ); treeFlattener = new MatTreeFlattener( this._transformer, node => node.level, node => node.expandable, node => node.children, ); dataSource = new MatTreeFlatDataSource(this.treeControl, this.treeFlattener); // 初始根目录数据赋值 this.dataSource.data = TREE_DATA;
步骤1:模板绑定节点展开事件
在mat-tree标签上绑定展开事件,触发子级加载逻辑:
<mat-tree [dataSource]="dataSource" [treeControl]="treeControl" (expandChange)="handleNodeExpand($event)"> <!-- 树节点渲染内容,和基础示例保持一致即可 --> <mat-tree-node *matTreeNodeDef="let node" matTreeNodePadding> {{node.name}} </mat-tree-node> <mat-tree-node *matTreeNodeDef="let node; when: hasChild" matTreeNodePadding> <button mat-icon-button matTreeNodeToggle [attr.aria-label]="'toggle ' + node.name"> <mat-icon class="mat-icon-rtl-mirror"> {{treeControl.isExpanded(node) ? 'expand_more' : 'chevron_right'}} </mat-icon> </button> {{node.name}} </mat-tree-node> </mat-tree>
补充hasChild判断方法:
hasChild = (_: number, node: DirectoryItemFlatNode) => node.expandable;
步骤2:实现懒加载子目录逻辑
编写节点展开回调,拉取子级数据后更新数据源:
// 节点展开回调 async handleNodeExpand(event: {expanded: boolean, node: DirectoryItemFlatNode}) { // 仅处理展开动作、且子级未加载的节点 if (!event.expanded || event.node.isLoaded) return; // 1. 根据当前扁平节点,找到对应的原始嵌套节点 const parentNestedNode = this.findNestedNode(this.dataSource.data, event.node); if (!parentNestedNode) return; // 2. 调用接口拉取当前父节点对应的子目录数据,替换为你实际的业务请求 const childList = await this.directoryService.getChildDirById(parentNestedNode.id); // 3. 给原始父节点赋值子级,标记已加载 parentNestedNode.children = childList; parentNestedNode.isLoaded = true; event.node.isLoaded = true; // 4. 重新赋值数据源触发树重渲染,已展开状态会自动保留 this.dataSource.data = [...this.dataSource.data]; } // 递归查找对应的原始嵌套节点 private findNestedNode(nodeList: DirectoryItemNode[], targetFlatNode: DirectoryItemFlatNode): DirectoryItemNode | null { for (const node of nodeList) { // 用唯一id匹配,准确性更高 if (node.id === targetFlatNode.id) { return node; } if (node.children?.length) { const found = this.findNestedNode(node.children, targetFlatNode); if (found) return found; } } return null; }
步骤3:实现手动向父节点新增子项
addChildToParent(parentNode: DirectoryItemFlatNode, newChild: DirectoryItemNode) { // 找到对应的原始父节点 const parentNestedNode = this.findNestedNode(this.dataSource.data, parentNode); if (!parentNestedNode) return; // 初始化子级数组(如果之前没有的话) if (!parentNestedNode.children) { parentNestedNode.children = []; } // 新增子项 parentNestedNode.children.push(newChild); // 刷新数据源 this.dataSource.data = [...this.dataSource.data]; // 可选:自动展开父节点展示新增的子项 this.treeControl.expand(parentNode); }
内容的提问来源于stack exchange,提问作者user8230558
相关产品推荐
相关产品推荐

