Angular Mat Table三层可展开行实现问题求助
三层嵌套MatTable(User>>Address>>Block)修复方案
你的代码存在以下关键问题导致Block层无法显示:
- 未将Address的blocks转换为MatTableDataSource:初始化时仅处理了User的addresses,未处理每个Address下的blocks数组,导致模板中无法识别
element.blocks?.data。 - Block层的表格结构嵌套错误:将Block的可展开列定义在了外层表格中,而非内层Address表格内部,导致无法关联到对应的Address行。
- Block表格的数据源和列定义错误:模板中Block表格的数据源写为
element.addresses,列定义误用了Address的列集合,而非Block的列。 - 未绑定Block层的展开/收起事件:内层Address行未绑定
toggleSubRow点击事件,无法触发Block层的展开。
修复后的完整代码
table-expandable-rows-example.ts
import { Component, ViewChild, ViewChildren, QueryList, ChangeDetectorRef } from '@angular/core'; import { animate, state, style, transition, trigger } from '@angular/animations'; import { MatSort } from '@angular/material/sort'; import { MatTableDataSource, MatTable } from '@angular/material/table'; @Component({ selector: 'table-expandable-rows-example', styleUrls: ['table-expandable-rows-example.css'], templateUrl: 'table-expandable-rows-example.html', animations: [ trigger('detailExpand', [ state('collapsed', style({ height: '0px', minHeight: '0' })), state('expanded', style({ height: '*' })), transition('expanded <=> collapsed', animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)')), ]), ], }) export class TableExpandableRowsExample { @ViewChild('outerSort', { static: true }) sort: MatSort; @ViewChildren('innerSort') innerSort: QueryList<MatSort>; @ViewChildren('innerTables') innerTables: QueryList<MatTable<Address>>; // 添加Block表格的Sort和Table引用 @ViewChildren('blockSort') blockSort: QueryList<MatSort>; @ViewChildren('blockTables') blockTables: QueryList<MatTable<Block>>; dataSource: MatTableDataSource<User>; usersData: User[] = []; columnsToDisplay = ['name', 'email', 'phone']; innerDisplayedColumns = ['street', 'zipCode', 'city']; subBlockDisplayedColumns = ['name', 'level', 'unitnumber']; expandedElement: User | null; expandedSubElement: Address | null; constructor( private cd: ChangeDetectorRef ) { } ngOnInit() { USERS.forEach(user => { if (user.addresses && Array.isArray(user.addresses) && user.addresses.length) { // 处理每个Address的blocks,转换为MatTableDataSource const processedAddresses = user.addresses.map(address => { if (address.blocks && Array.isArray(address.blocks) && address.blocks.length) { return {...address, blocks: new MatTableDataSource(address.blocks)}; } return address; }); this.usersData = [...this.usersData, {...user, addresses: new MatTableDataSource(processedAddresses)}]; } else { this.usersData = [...this.usersData, user]; } }); this.dataSource = new MatTableDataSource(this.usersData); this.dataSource.sort = this.sort; } toggleRow(element: User) { element.addresses && (element.addresses as MatTableDataSource<Address>).data.length ? (this.expandedElement = this.expandedElement === element ? null : element) : null; this.cd.detectChanges(); this.innerTables.forEach((table, index) => (table.dataSource as MatTableDataSource<Address>).sort = this.innerSort.toArray()[index]); } toggleSubRow(element: Address) { element.blocks && (element.blocks as MatTableDataSource<Block>).data.length ? (this.expandedSubElement = this.expandedSubElement === element ? null : element) : null; this.cd.detectChanges(); // 更新Block表格的排序 this.blockTables.forEach((table, index) => (table.dataSource as MatTableDataSource<Block>).sort = this.blockSort.toArray()[index]); } applyFilter(filterValue: string) { this.innerTables.forEach((table, index) => (table.dataSource as MatTableDataSource<Address>).filter = filterValue.trim().toLowerCase()); } // 可选:添加Block层的过滤方法 applyBlockFilter(filterValue: string) { this.blockTables.forEach((table, index) => (table.dataSource as MatTableDataSource<Block>).filter = filterValue.trim().toLowerCase()); } } export interface User { name: string; email: string; phone: string; addresses?: Address[] | MatTableDataSource<Address>; } export interface Address { street: string; zipCode: string; city: string; blocks?: Block[] | MatTableDataSource<Block>; } export interface Block { name: string; level: string; unitnumber: string; } const USERS: User[] = [ { name: "Mason", email: "mason@test.com", phone: "9864785214", addresses: [ { street: "Street 1", zipCode: "78542", city: "Kansas", blocks: [ { name: "Blk 11", level: "Lvl 1", unitnumber: "01-01" }, { name: "Blk 22", level: "Lvl 2", unitnumber: "02-01" } ] }, { street: "Street 2", zipCode: "78554", city: "Texas", blocks: [ { name: "Blk 33", level: "Lvl 3", unitnumber: "03-02" }, { name: "Blk 44", level: "Lvl 4", unitnumber: "04-02" } ] } ] }, { name: "Jason", email: "jason@test.com", phone: "7856452187", addresses: [ { street: "Street 5", zipCode: "23547", city: "Utah" }, { street: "Street 5", zipCode: "23547", city: "Ohio" } ] } ];
table-expandable-rows-example.html
<table mat-table #outerSort="matSort" [dataSource]="dataSource" multiTemplateDataRows class="mat-elevation-z8" matSort> <ng-container matColumnDef="{{column}}" *ngFor="let column of columnsToDisplay"> <th mat-header-cell *matHeaderCellDef mat-sort-header> {{column}} </th> <td mat-cell *matCellDef="let element"> {{element[column]}} </td> </ng-container> <!-- User展开层:显示Address表格 --> <ng-container matColumnDef="expandedDetail"> <td mat-cell *matCellDef="let element" [attr.colspan]="columnsToDisplay.length"> <div class="example-element-detail" *ngIf="element.addresses?.data.length" [@detailExpand]="element == expandedElement ? 'expanded' : 'collapsed'"> <div class="inner-table mat-elevation-z8" *ngIf="expandedElement"> <mat-form-field> <input matInput (keyup)="applyFilter($event.target.value)" placeholder="Filter Addresses"> </mat-form-field> <!-- Address表格 --> <table #innerTables mat-table #innerSort="matSort" [dataSource]="element.addresses" matSort multiTemplateDataRows> <ng-container matColumnDef="{{innerColumn}}" *ngFor="let innerColumn of innerDisplayedColumns"> <th mat-header-cell *matHeaderCellDef mat-sort-header> {{innerColumn}} </th> <td mat-cell *matCellDef="let element"> {{element[innerColumn]}} </td> </ng-container> <!-- Address展开层:显示Block表格 --> <ng-container matColumnDef="expandedSubDetail"> <td mat-cell *matCellDef="let element" [attr.colspan]="innerDisplayedColumns.length"> <div class="example-element-detail" *ngIf="element.blocks?.data.length" [@detailExpand]="element == expandedSubElement ? 'expanded' : 'collapsed'"> <div class="inner-table mat-elevation-z8" *ngIf="expandedSubElement"> <mat-form-field> <input matInput (keyup)="applyBlockFilter($event.target.value)" placeholder="Filter Blocks"> </mat-form-field> <!-- Block表格 --> <table #blockTables mat-table #blockSort="matSort" [dataSource]="element.blocks" matSort> <ng-container matColumnDef="{{blockColumn}}" *ngFor="let blockColumn of subBlockDisplayedColumns"> <th mat-header-cell *matHeaderCellDef mat-sort-header> {{blockColumn}} </th> <td mat-cell *matCellDef="let element"> {{element[blockColumn]}} </td> </ng-container> <tr mat-header-row *matHeaderRowDef="subBlockDisplayedColumns"></tr> <tr mat-row *matRowDef="let row; columns: subBlockDisplayedColumns;"></tr> </table> </div> </div> </td> </ng-container> <tr mat-header-row *matHeaderRowDef="innerDisplayedColumns"></tr> <!-- Address行:绑定展开事件 --> <tr mat-row *matRowDef="let element; columns: innerDisplayedColumns;" [class.example-element-row]="element.blocks?.data.length" [class.example-expanded-row]="expandedSubElement === element" (click)="toggleSubRow(element)"></tr> <!-- Address展开行 --> <tr mat-row *matRowDef="let row; columns: ['expandedSubDetail']" class="example-detail-row"></tr> </table> </div> </div> </td> </ng-container> <tr mat-header-row *matHeaderRowDef="columnsToDisplay"></tr> <!-- User行:绑定展开事件 --> <tr mat-row *matRowDef="let element; columns: columnsToDisplay;" [class.example-element-row]="element.addresses?.data.length" [class.example-expanded-row]="expandedElement === element" (click)="toggleRow(element)"></tr> <!-- User展开行 --> <tr mat-row *matRowDef="let row; columns: ['expandedDetail']" class="example-detail-row"></tr> </table>
关键修复点说明
- 数据初始化处理:遍历每个User的Address,将blocks数组转换为
MatTableDataSource,确保模板能识别element.blocks?.data。 - 嵌套结构调整:将Block的可展开列和表格嵌套在Address表格内部,实现层级关联。
- 事件绑定修正:给Address行添加
toggleSubRow点击事件,触发Block层的展开/收起。 - 数据源和列修正:Block表格的数据源改为
element.blocks,列集合使用subBlockDisplayedColumns。 - 排序支持:添加Block表格的Sort引用,在展开时更新排序配置。
内容的提问来源于stack exchange,提问作者GeetaAmbrage
相关产品推荐
相关产品推荐

