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

如何在Angular中实现点击父项懒加载API数据的层级树列表

Angular 实现懒加载层级嵌套树列表最优方案

核心最优实现为自引用递归组件 + 单节点独立状态管理,无需依赖重量型第三方树组件,逻辑完全可控,天然支持无限层级嵌套与按需加载。


核心实现逻辑

  • 用可自引用的递归组件承接节点渲染,无需提前写死嵌套层级,任意深度的子节点都能自动适配
  • 每个节点独立维护加载状态、展开状态、子节点数据,仅在用户点击对应节点时触发API请求,不会提前拉取未展开节点的数据
  • 配合trackBy做列表渲染优化,避免同层级节点变动时整树重绘,大数量级下性能表现稳定

具体实现步骤

1. 定义通用树节点数据结构

// tree-node.interface.ts
export interface TreeNode {
  id: string | number;
  label: string;
  children?: TreeNode[];
  isLoading?: boolean;
  isExpanded?: boolean;
  hasChildren?: boolean; // 由接口返回,标识当前节点是否存在子节点,避免无效请求
}

2. 开发递归树节点组件(核心)

组件可在自身模板内引用自身选择器,天然支持无限层级渲染:

// tree-node.component.ts
import { Component, Input } from '@angular/core';
import { TreeNode } from './tree-node.interface';
import { DataService } from './data.service'; // 替换为项目自身的API请求服务

@Component({
  selector: 'app-tree-node',
  template: `
    <div class="tree-node-item" (click)="handleNodeClick(node)">
      <span class="expand-icon">{{ node.isExpanded ? '▼' : node.hasChildren ? '▶' : '•' }}</span>
      <span class="node-label">{{ node.label }}</span>
      <span *ngIf="node.isLoading" class="loading-tip">加载中...</span>
    </div>
    <!-- 节点展开且已加载子节点时,递归渲染当前组件 -->
    <div class="children-wrapper" *ngIf="node.isExpanded && node.children?.length">
      <app-tree-node
        *ngFor="let child of node.children; trackBy: trackByNodeId"
        [node]="child">
      </app-tree-node>
    </div>
  `,
  styles: [`
    .tree-node-item {
      padding: 8px 12px;
      cursor: pointer;
      display: flex;
      align-items: center;
      gap: 8px;
    }
    .tree-node-item:hover {
      background: #f5f5f5;
    }
    .children-wrapper {
      padding-left: 24px;
    }
    .loading-tip {
      font-size: 12px;
      color: #999;
    }
  `]
})
export class TreeNodeComponent {
  @Input() node!: TreeNode;

  constructor(private dataService: DataService) {}

  handleNodeClick(node: TreeNode) {
    // 无子节点的项直接跳过逻辑
    if (!node.hasChildren) return;
    // 已加载过子节点的项直接切换展开/收起,不重复发请求
    if (node.children?.length) {
      node.isExpanded = !node.isExpanded;
      return;
    }
    // 触发子节点加载
    node.isLoading = true;
    this.dataService.getChildrenByNodeId(node.id).subscribe({
      next: (children) => {
        node.children = children;
        node.isExpanded = true;
        node.isLoading = false;
      },
      error: () => {
        node.isLoading = false;
        // 可在此处自定义错误提示逻辑
      }
    })
  }

  trackByNodeId(index: number, node: TreeNode) {
    return node.id;
  }
}

3. 根节点初始化

根组件仅需加载第一层列表(即需求中提到的list1-list4),后续层级由递归组件自动处理:

// app.component.ts
import { Component, OnInit } from '@angular/core';
import { TreeNode } from './tree-node.interface';
import { DataService } from './data.service';

@Component({
  selector: 'app-root',
  template: `
    <div class="tree-container">
      <app-tree-node
        *ngFor="let rootNode of rootNodeList; trackBy: trackByNodeId"
        [node]="rootNode">
      </app-tree-node>
    </div>
  `
})
export class AppComponent implements OnInit {
  rootNodeList: TreeNode[] = [];

  constructor(private dataService: DataService) {}

  ngOnInit() {
    // 初始化加载第一层根节点
    this.dataService.getRootNodes().subscribe(nodes => {
      this.rootNodeList = nodes;
    })
  }

  trackByNodeId(index: number, node: TreeNode) {
    return node.id;
  }
}

方案注意事项

记得在@NgModule的declarations数组中声明TreeNodeComponent,Angular原生支持组件在自身模板内递归引用,无需额外配置。如果单层级节点量级超过1000条,可配合Angular CDK的虚拟滚动做性能优化,交互流畅度会明显提升。


方案优势

  • 无额外第三方依赖,对包体积几乎无影响,逻辑完全可控
  • 天然支持无限层级嵌套,无论子节点深度是4层还是10层都无需修改核心代码
  • 懒加载逻辑精准,仅用户点击的节点会触发API请求,首屏加载速度快,接口压力小
  • 节点状态完全独立,展开、加载、收起操作互不干扰

内容的提问来源于stack exchange,提问作者Janus Karthik

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.03 07:45:40