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

如何为mat-tree组件的大量子节点实现虚拟滚动?

解决Mat-Tree展开大量子节点卡顿的虚拟滚动实现方案

核心方案

Angular CDK的虚拟滚动组件(CdkVirtualScroll)可以适配树形结构,核心是给每个展开的文件夹节点的子列表单独添加虚拟滚动容器,只渲染可视区域内的节点,避免一次性渲染大量DOM元素导致的卡顿。

实现步骤

  1. 引入CDK滚动模块
    在你的模块文件中导入ScrollingModule:
import { ScrollingModule } from '@angular/cdk/scrolling';

@NgModule({
  imports: [
    // 其他模块
    ScrollingModule
  ]
})
export class YourModule { }
  1. 改造模板结构
    调整原有的mat-tree模板,为文件夹节点的子节点区域添加虚拟滚动容器,并优化层级缩进的实现(用CSS代替*ngFor生成span,减少DOM元素):
<mat-tree [dataSource]="dataSource" [treeControl]="treeControl">
  <!-- 文件节点模板 -->
  <mat-tree-node *matTreeNodeDef="let node" class="tree-node">
    <div fxLayout="row" fxFlex>
      <div class="indent" [style.margin-left.px]="treeControl.getLevel(node) * 24"></div>
      <mat-checkbox
        [checked]="node.checked"
        (change)="onCheckboxChange(node)"
        [disabled]="!node.checkeable"
        (click)="$event.stopPropagation()"
      >
        <button mat-icon-button><mat-icon> text_snippet </mat-icon></button>
      </mat-checkbox>
      <span
        class="file-name"
        fxLayoutAlign="center center"
        (click)="downloadFile(node.file_ancillary_oid, node.size)"
      >
        {{ getFileName(node) }}
      </span>
    </div>
    <div fxFlex="20" fxLayoutAlign="center center">{{ formatSize(node.size) }}</div>
  </mat-tree-node>

  <!-- 文件夹节点模板 -->
  <mat-tree-node *matTreeNodeDef="let node; when: isFolder" class="tree-node border-top">
    <div fxLayout="row" fxFlex>
      <div class="indent" [style.margin-left.px]="treeControl.getLevel(node) * 24"></div>
      <mat-checkbox [checked]="node.checked" (change)="onCheckboxChange(node)" [disabled]="!node.checkeable">
        <button mat-icon-button matTreeNodeToggle [attr.aria-label]="'toggle ' + node.name">
          <mat-icon> {{ treeControl.isExpanded(node) ? 'folder_open' : 'folder' }} </mat-icon>
        </button>
      </mat-checkbox>
      <span fxLayoutAlign="center center" matTreeNodeToggle class="file-name"> {{ node.name }} </span>
    </div>
    <div fxFlex="20" fxLayoutAlign="center center">{{ node.size }}</div>
    
    <!-- 虚拟滚动容器包裹子节点 -->
    <div class="child-container">
      <cdk-virtual-scroll-viewport itemSize="48" class="virtual-scroll-viewport">
        <mat-tree-node *matTreeNodeDef="let child; when: hasChild" matTreeNodeOutlet></mat-tree-node>
        <mat-tree-node *matTreeNodeDef="let child" matTreeNodeOutlet></mat-tree-node>
      </cdk-virtual-scroll-viewport>
    </div>
  </mat-tree-node>
</mat-tree>
  1. 添加CSS样式
    给虚拟滚动容器和节点添加必要的样式,确保容器有固定高度,节点布局正确:
.tree-node {
  height: 48px; /* 要和itemSize一致 */
  display: flex;
  align-items: center;
}

.virtual-scroll-viewport {
  height: 300px; /* 可视区域高度,可根据需求调整 */
  width: 100%;
}

.child-container {
  margin-left: 24px; /* 对应层级缩进的距离 */
}

.indent {
  width: 24px;
}
  1. 组件类调整
    确保你的TreeControl和DataSource支持按需获取子节点,如果是一次性加载所有数据,需要确保dataSource的connect方法返回的流能正确响应节点展开/折叠事件:
// 示例:如果用FlatTreeControl和FlatTreeDataSource
treeControl = new FlatTreeControl<YourNodeType>(
  node => node.level,
  node => node.expandable
);

dataSource = new MatTreeFlatDataSource(this.treeControl, new YourTreeDataSource());

// 确保hasChild方法正确判断
hasChild = (_: number, node: YourNodeType) => node.expandable;

关键说明

  • itemSize设置:itemSize必须和节点的实际高度一致,这样虚拟滚动才能准确计算可视区域内的节点数量。
  • 层级缩进优化:用style.margin-left代替*ngFor生成span,减少不必要的DOM元素,提升性能。
  • 动态加载子节点:如果你的文件夹子节点数量极大,可以结合懒加载,在节点展开时才请求后端数据,进一步减少初始加载压力。

内容的提问来源于stack exchange,提问作者Ramón Parejo Cuéllar

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 19:37:14