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

Angular Mat Table三层可展开行实现问题求助

三层嵌套MatTable(User>>Address>>Block)修复方案

你的代码存在以下关键问题导致Block层无法显示:

  1. 未将Address的blocks转换为MatTableDataSource:初始化时仅处理了User的addresses,未处理每个Address下的blocks数组,导致模板中无法识别element.blocks?.data。
  2. Block层的表格结构嵌套错误:将Block的可展开列定义在了外层表格中,而非内层Address表格内部,导致无法关联到对应的Address行。
  3. Block表格的数据源和列定义错误:模板中Block表格的数据源写为element.addresses,列定义误用了Address的列集合,而非Block的列。
  4. 未绑定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>

关键修复点说明

  1. 数据初始化处理:遍历每个User的Address,将blocks数组转换为MatTableDataSource,确保模板能识别element.blocks?.data。
  2. 嵌套结构调整:将Block的可展开列和表格嵌套在Address表格内部,实现层级关联。
  3. 事件绑定修正:给Address行添加toggleSubRow点击事件,触发Block层的展开/收起。
  4. 数据源和列修正:Block表格的数据源改为element.blocks,列集合使用subBlockDisplayedColumns。
  5. 排序支持:添加Block表格的Sort引用,在展开时更新排序配置。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 12:50:28