如何用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>
关键说明
- 列对齐保障:所有行都在同一个表格内,列宽度由表头统一控制,天然对齐,彻底解决嵌套表格的列错位问题。
- 展开状态管理:用
Set存储展开的父行ID,不修改原始数据,符合不可变数据的最佳实践。 - 缩进控制:通过
row.level动态计算子行左边距,层级越深缩进越大,视觉上清晰区分父子关系。
内容的提问来源于stack exchange,提问作者Eric
相关产品推荐
相关产品推荐

