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

如何向已有的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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.25 04:36:02