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

如何用Angular mat-table实现可展开折叠树形结构并对齐列?

Angular Mat-Table 实现可展开/折叠的层级数据(单表列对齐方案)

核心思路

用单表+扁平化数据源实现,彻底避免嵌套表格导致的列对齐问题。将嵌套的父子数据转换成带层级标识的扁平数组,通过控制父行的展开状态来显示/隐藏对应子行,同时给子行添加缩进区分层级。

步骤1:定义扩展数据结构

给原始Entity扩展属性,用于记录层级、唯一ID和父节点关联关系:

export interface Entity {
  name: string;
  age: number;
  children?: Entity[];
}

// 扩展后的表格行数据结构
interface TableEntity extends Entity {
  id: string;       // 唯一标识,用于关联父子
  level: number;    // 层级:0为父行,1为子行,以此类推
  parentId?: string;// 父节点ID
}

步骤2:扁平化嵌套数据

编写工具函数,把原始嵌套数据转换成适合表格展示的扁平数组:

const Entities: Entity[] = [
  {
    name: 'P1',
    age: 40,
    children: [
      { name: 'C1a', age: 18 },
      { name: 'C1b', age: 15 } // 注:你原数据中此处写的是protocol,已修正为age
    ]
  },
  {
    name: 'P2',
    age: 43,
    children: [
      { name: 'C2a', age: 21 },
      { name: 'C2b', age: 22 }
    ]
  }
];

private flattenEntities(entities: Entity[], level = 0, parentId?: string): TableEntity[] {
  let flatData: TableEntity[] = [];
  entities.forEach((entity, index) => {
    // 生成唯一ID:父行用parent-索引,子行用父ID-子索引
    const rowId = parentId ? `${parentId}-${index}` : `parent-${index}`;
    flatData.push({
      ...entity,
      id: rowId,
      level,
      parentId
    });
    // 递归处理子节点
    if (entity.children?.length) {
      flatData = [...flatData, ...this.flattenEntities(entity.children, level + 1, rowId)];
    }
  });
  return flatData;
}

步骤3:组件逻辑实现

在组件中初始化数据源,添加展开/折叠的控制逻辑:

import { Component, OnInit } from '@angular/core';
import { MatTableDataSource } from '@angular/material/table';

// ... 上面的接口和数据定义

@Component({
  selector: 'app-hierarchy-table',
  templateUrl: './hierarchy-table.component.html',
  styleUrls: ['./hierarchy-table.component.css']
})
export class HierarchyTableComponent implements OnInit {
  displayedColumns: string[] = ['name', 'age'];
  dataSource = new MatTableDataSource<TableEntity>();
  // 用Set存储展开的父行ID,不修改原始数据
  expandedParentIds = new Set<string>();

  ngOnInit() {
    const flattenedData = this.flattenEntities(Entities);
    this.dataSource.data = flattenedData;
    // 设置过滤规则,控制子行显示
    this.dataSource.filterPredicate = (row: TableEntity) => {
      // 父行始终显示
      if (row.level === 0) return true;
      // 子行仅当父行展开时显示
      return this.expandedParentIds.has(row.parentId!);
    };
  }

  // 切换父行展开/折叠状态
  toggleParent(row: TableEntity) {
    if (this.expandedParentIds.has(row.id)) {
      this.expandedParentIds.delete(row.id);
    } else {
      this.expandedParentIds.add(row.id);
    }
    // 触发表格过滤更新
    this.dataSource.filter = Date.now().toString();
  }
}

步骤4:模板实现

编写mat-table模板,给父行添加点击事件和展开图标,子行添加动态缩进:

<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
  <!-- 名称列 -->
  <ng-container matColumnDef="name">
    <th mat-header-cell *matHeaderCellDef> name </th>
    <td mat-cell *matCellDef="let row">
      <!-- 父行:添加点击事件和展开图标 -->
      <div *ngIf="row.level === 0" 
           (click)="toggleParent(row)" 
           style="cursor: pointer; display: flex; align-items: center; gap: 8px;">
        <mat-icon>{{expandedParentIds.has(row.id) ? 'expand_less' : 'expand_more'}}</mat-icon>
        <span>{{row.name}}</span>
      </div>
      <!-- 子行:根据层级设置缩进 -->
      <div *ngIf="row.level > 0" 
           [style.margin-left.px]="row.level * 24">
        {{row.name}}
      </div>
    </td>
  </ng-container>

  <!-- 年龄列 -->
  <ng-container matColumnDef="age">
    <th mat-header-cell *matHeaderCellDef> age </th>
    <td mat-cell *matCellDef="let row"> {{row.age}} </td>
  </ng-container>

  <!-- 表头和行定义 -->
  <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
  <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>

关键说明

  1. 列对齐保障:所有行都在同一个表格内,列宽度由表头统一控制,天然对齐,彻底解决嵌套表格的列错位问题。
  2. 展开状态管理:用Set存储展开的父行ID,不修改原始数据,符合不可变数据的最佳实践。
  3. 缩进控制:通过row.level动态计算子行左边距,层级越深缩进越大,视觉上清晰区分父子关系。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 00:25:08