我正在尝试创建一个可重用的角度材质表,其中包含投影的列和行定义。 Angular 在其文档示例here中提到了这一点。单层内容投影工作得很好,但是(IMO)给“消费者”组件留下了大量重复性工作,所以我想为表格创建一个“包装器”组件来处理排序、分页、过滤和其他自定义功能。但我仍然希望能够从 Consumer 组件投影列和行定义,并且我似乎无法使嵌套内容投影工作,因为 Table 组件在其 @ContentChildren() 中找不到任何内容。这是代码,希望它会更有意义:)
表格组件
@Component({
selector: 'app-table',
template: `
<table mat-table [dataSource]="dataSource">
<ng-content />
</table>
`,
standalone: true,
})
export class TableComponent<T> implements AfterContentInit {
@ViewChild(MatTable, { static: true }) table: MatTable<T>;
@ContentChildren(MatColumnDef) columnDefs: QueryList<MatColumnDef>;
@ContentChildren(MatHeaderRowDef) headerRowDefs: QueryList<MatHeaderRowDef>;
@ContentChildren(MatRowDef) rowDefs: QueryList<MatRowDef<T>>;
@Input() dataSource: MatTableDataSource<T>;
ngAfterContentInit() {
this.columnDefs.forEach((columnDef) => this.table.addColumnDef(columnDef));
this.rowDefs.forEach((rowDef) => this.table.addRowDef(rowDef));
this.headerRowDefs.forEach((headerRowDef) =>
this.table.addHeaderRowDef(headerRowDef)
);
// query lists are empty with nested projection
console.log(this.columnDefs, this.rowDefs, this.headerRowDefs);
}
}
TableWrapper组件
@Component({
selector: 'app-table-wrapper',
template: `
<app-table [dataSource]="dataSource">
<ng-content />
</app-table>
`,
standalone: true,
})
export class TableWrapperComponent<T> {
@Input() dataSource: MatTableDataSource<T>;
}
消费者组件
export interface PeriodicElement {
name: string;
position: number;
weight: number;
symbol: string;
}
@Component({
selector: 'app-consumer',
template: `
<app-table-wrapper [dataSource]="dataSource">
<ng-container matColumnDef="position">
<th mat-header-cell *matHeaderCellDef> Position </th>
<td mat-cell *matCellDef="let element"> {{ element.position }} </td>
</ng-container>
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef> Name </th>
<td mat-cell *matCellDef="let element"> {{ element.name }} </td>
</ng-container>
<ng-container matColumnDef="weight">
<th mat-header-cell *matHeaderCellDef> Weight </th>
<td mat-cell *matCellDef="let element"> {{ element.weight }} </td>
</ng-container>
<ng-container matColumnDef="symbol">
<th mat-header-cell *matHeaderCellDef> Symbol </th>
<td mat-cell *matCellDef="let element"> {{ element.symbol }} </td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
</app-table-wrapper>
`,
standalone: true,
})
export class ConsumerComponent<T> {
dataSource: MatTableDataSource<PeriodicElement> = new MatTableDataSource([
{ position: 1, name: 'Hydrogen', weight: 1.0079, symbol: 'H' },
{ position: 2, name: 'Helium', weight: 4.0026, symbol: 'He' },
{ position: 3, name: 'Lithium', weight: 6.941, symbol: 'Li' },
{ position: 4, name: 'Beryllium', weight: 9.0122, symbol: 'Be' },
{ position: 5, name: 'Boron', weight: 10.811, symbol: 'B' },
{ position: 6, name: 'Carbon', weight: 12.0107, symbol: 'C' },
{ position: 7, name: 'Nitrogen', weight: 14.0067, symbol: 'N' },
{ position: 8, name: 'Oxygen', weight: 15.9994, symbol: 'O' },
{ position: 9, name: 'Fluorine', weight: 18.9984, symbol: 'F' },
{ position: 10, name: 'Neon', weight: 20.1797, symbol: 'Ne' },
]);
displayedColumns: string[] = ['position', 'name', 'weight', 'symbol'];
}
所以..我希望实现的是简单的嵌套内容投影,但正如我所说,表组件将这些查询列表为空。如果不是嵌套投影,同一段代码可以完美运行。拥有这个中间层 - 包装器组件将极大地受益于清理可重复的功能,同时保持表组件的干净和简单。有什么想法可以让这种事情与表格一起使用吗?
附注很抱歉没有 stackblitz 示例,它陷入了“Booting WebContainer”问题。 另外,抱歉,如果我在提问时搞砸了任何约定,这是我的第一篇文章,我不确定是否将其发布在这里或在 Angular github 问题页面上:/
使用投影列和行定义为 Angular Material Table 创建包装器组件的方法是正确的。但是,当使用嵌套内容投影时,您需要确保投影内容可以在包装器组件中正确访问。
在您的
TableWrapperComponent
中,您将内容投影到 app-table
中,但您还需要确保投影内容(即列和行定义)可在 TableComponent
内的 TableWrapperComponent
内访问。
以下是如何修改代码以实现嵌套内容投影:
TableWrapperComponent
以将投影内容传递到TableComponent
:@Component({
selector: 'app-table-wrapper',
template: `
<app-table [dataSource]="dataSource">
<ng-content></ng-content>
</app-table>
`,
standalone: true,
})
export class TableWrapperComponent<T> {
@Input() dataSource: MatTableDataSource<T>;
}
TableComponent
以正确处理嵌套内容投影:@Component({
selector: 'app-table',
template: `
<table mat-table [dataSource]="dataSource">
<ng-container *ngFor="let columnDef of columnDefs">
<ng-container [matColumnDef]="columnDef.name">
<th mat-header-cell *matHeaderCellDef>{{ columnDef.name }}</th>
<td mat-cell *matCellDef="let element">{{ element[columnDef.name] }}</td>
</ng-container>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
</table>
`,
standalone: true,
})
export class TableComponent<T> implements AfterContentInit {
@ViewChild(MatTable, { static: true }) table: MatTable<T>;
@ContentChildren(MatColumnDef) columnDefs: QueryList<MatColumnDef>;
@ContentChildren(MatHeaderRowDef) headerRowDefs: QueryList<MatHeaderRowDef>;
@ContentChildren(MatRowDef) rowDefs: QueryList<MatRowDef<T>>;
@Input() dataSource: MatTableDataSource<T>;
ngAfterContentInit() {
this.columnDefs.forEach((columnDef) => this.table.addColumnDef(columnDef));
this.rowDefs.forEach((rowDef) => this.table.addRowDef(rowDef));
this.headerRowDefs.forEach((headerRowDef) =>
this.table.addHeaderRowDef(headerRowDef)
);
// Ensure that the projected content is accessible
console.log(this.columnDefs, this.rowDefs, this.headerRowDefs);
}
// Extract displayed column names
get displayedColumns(): string[] {
return this.columnDefs.map((columnDef) => columnDef.name);
}
}
通过这些更改,
TableComponent
应正确处理来自 TableWrapperComponent
的嵌套内容投影,允许您在 ConsumerComponent
内定义列和行,并让表格正确呈现它们。确保根据您的具体要求调整任何其他逻辑。
如果您遇到任何问题或有其他疑问,请随时询问!