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

如何正确将数据源从父组件传递到Angular Material表格子组件?

Angular Material可复用表格组件数据传递问题

我正在使用带排序功能的Angular Material表格,计划将其封装为可复用组件,父组件只需传入columns和dataSource,写法如下:

<app-reports-mat-table [columns]="columns" [dataSource]="dataSource"></app-reports-mat-table>

但目前只有columns能正常显示,子组件中的dataSource始终为undefined。如果给子组件加上*ngIf="dataSource"判断,页面则完全不显示任何内容。请告知如何正确将dataSource传递到子组件,以及如何实现该表格的可复用。


父组件代码

模板:

<app-reports-mat-table [columns]="columns" [dataSource]="dataSource"></app-reports-mat-table>

TS代码:

export interface FundingNeeds {
  pdoName: string;
  pdoIdentifiedAdvocacy?: string;
  program?: string;
  yearProposed?: string;
  unitOfItem?: string;
  location?: string;
}

@Component({
  selector: 'app-report-funding-needs',
  templateUrl: './report-funding-needs.component.html',
  styleUrls: ['./report-funding-needs.component.css'],
})
export class ReportFundingNeedsComponent {
  dataSource: MatTableDataSource<FundingNeeds>;

  columns = [
    {
      columnDef: 'pdoName',
      header: 'PDO Name',
      cell: (element: FundingNeeds) => `${element.pdoName}`,
    },
    {
      columnDef: 'pdoIdentifiedAdvocacy',
      header: 'PDO Identified Advocacy',
      cell: (element: FundingNeeds) => `${element.pdoIdentifiedAdvocacy}`,
    },
    {
      columnDef: 'program',
      header: 'Program',
      cell: (element: FundingNeeds) => `${element.program}`,
    },
    {
      columnDef: 'yearProposed',
      header: 'Year Proposed',
      cell: (element: FundingNeeds) => `${element.yearProposed}`,
    },
    {
      columnDef: 'unitOfItem',
      header: 'Unit of Main Item to Procure/Build',
      cell: (element: FundingNeeds) => `${element.unitOfItem}`,
    },
    {
      columnDef: 'location',
      header: 'Location',
      cell: (element: FundingNeeds) => `${element.location}`,
    },
  ];

  lastEntry = new Date();

  constructor(private reportFundingNeedsService: ReportFundingNeedsService) {}
  
  ngOnInit(): void {
    this.reportFundingNeedsService
      .getFundingNeedsSummary()
      .pipe(take(1))
      .subscribe((response: any) => {
        const fundingNeedsData: FundingNeeds[] = [];
        response.results.pdos.forEach(pdo => {
          pdo.pdo_advocacies.forEach(advocacy => {
            advocacy.pdo_programs.forEach(program => {
              fundingNeedsData.push({
                pdoName: pdo.name,
                pdoIdentifiedAdvocacy: advocacy.advocacy,
                program: program.program,
                yearProposed: program.proposed_date,
                unitOfItem: program.admin_beneficiary_category ? program.admin_beneficiary_category.category : '',
                location:
                  program.pdo_program_locations.length > 0 ? program.pdo_program_locations[0].location.name : '',
              });
            });
          });
        });

        this.dataSource = new MatTableDataSource<FundingNeeds>(fundingNeedsData);
      });
  }
}

子组件代码

模板:

<table mat-table [dataSource]="dataSource" matSort (matSortChange)="announceSortChange($event)"
  class="mat-elevation-z8">

  <ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
    <th mat-header-cell *matHeaderCellDef mat-sort-header sortActionDescription="Sort by {{ column.header }}">
      {{column.header}}
    </th>
    <td mat-cell *matCellDef="let row">
      {{column.cell(row)}}
    </td>
  </ng-container>

  <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
  <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>

TS代码:

export class ReportsMatTableComponent implements AfterViewInit, OnInit {
  @Input() columns: {
    columnDef: string;
    header: string;
    cell: (element: any) => string;
  }[] = [];

  @Input() dataSource: MatTableDataSource<any>;

  displayedColumns: string[];

  constructor(private _liveAnnouncer: LiveAnnouncer) {}

  @ViewChild(MatSort) sort: MatSort;

  ngOnInit(): void {
    if (this.columns) {
      this.displayedColumns = this.columns.map(c => c.columnDef);
    }
  }

  ngAfterViewInit() {
    console.log(this.dataSource);
    
    if (this.dataSource) {
      console.log("child has data");
      
      this.dataSource.sort = this.sort;
    }
  }

  announceSortChange(sortState: Sort) {
    if (sortState.direction) {
      this._liveAnnouncer.announce(`Sorted ${sortState.direction}ending`);
    } else {
      this._liveAnnouncer.announce('Sorting cleared');
    }
  }
}

问题分析与解决方案

核心问题原因

  • 异步数据初始化时机不匹配:父组件的dataSource在HTTP请求回调中异步赋值,子组件ngAfterViewInit执行时,异步请求尚未完成,dataSource仍为undefined。
  • 未监听输入属性变化:子组件仅在初始化钩子中检查dataSource,父组件异步更新dataSource后,子组件不会自动触发排序器绑定。

具体解决方案

1. 父组件提前初始化空数据源

避免初始传递undefined,提前创建空的MatTableDataSource实例,后续异步请求完成后更新数据即可:

export class ReportFundingNeedsComponent {
  // 初始化空数据源
  dataSource = new MatTableDataSource<FundingNeeds>([]);
  
  // ...其他代码不变

  ngOnInit(): void {
    this.reportFundingNeedsService
      .getFundingNeedsSummary()
      .pipe(take(1))
      .subscribe((response: any) => {
        // ...数据转换逻辑不变
        
        // 直接更新数据源的data属性,而非重新创建实例
        this.dataSource.data = fundingNeedsData;
      });
  }
}

2. 子组件监听输入属性变化

实现OnChanges钩子,监听dataSource的变化,每当输入更新时重新绑定排序器:

import { SimpleChanges } from '@angular/core';

export class ReportsMatTableComponent implements AfterViewInit, OnInit, OnChanges {
  @Input() columns: {
    columnDef: string;
    header: string;
    cell: (element: any) => string;
  }[] = [];

  @Input() dataSource: MatTableDataSource<any> = new MatTableDataSource();

  displayedColumns: string[];

  constructor(private _liveAnnouncer: LiveAnnouncer) {}

  @ViewChild(MatSort) sort: MatSort;

  ngOnInit(): void {
    if (this.columns) {
      this.displayedColumns = this.columns.map(c => c.columnDef);
    }
  }

  ngOnChanges(changes: SimpleChanges) {
    // 当dataSource变化且排序器已初始化时,重新绑定排序
    if (changes['dataSource'] && this.sort) {
      this.dataSource.sort = this.sort;
    }
  }

  ngAfterViewInit() {
    // 初始绑定排序器
    if (this.dataSource) {
      this.dataSource.sort = this.sort;
    }
  }

  // ...announceSortChange方法不变
}

3. 优化子组件模板的ngIf判断(可选)

若要保留*ngIf,可判断数据源的data长度,确保初始空数据源时能显示表头:

<table mat-table [dataSource]="dataSource" matSort (matSortChange)="announceSortChange($event)"
  class="mat-elevation-z8" *ngIf="dataSource.data.length >= 0">
  <!-- ...模板内容不变 -->
</table>

可复用组件额外优化建议

  • 添加泛型支持:提升类型安全性,让组件适配不同数据结构:
export class ReportsMatTableComponent<T> implements AfterViewInit, OnInit, OnChanges {
  @Input() columns: {
    columnDef: string;
    header: string;
    cell: (element: T) => string;
  }[] = [];

  @Input() dataSource: MatTableDataSource<T> = new MatTableDataSource();
  
  // ...其他代码适配泛型
}
  • 扩展功能支持:可封装分页、过滤功能,通过输入输出属性暴露配置和事件;
  • 默认列配置:给columns设置合理默认值,避免空数组导致无表头显示。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 17:14:59