如何正确将数据源从父组件传递到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
相关产品推荐
相关产品推荐

