我没有尝试将组件传递到表格中,而是最终将表格传递给了一个 ComponentFactory,然后表格将负责从工厂实例化组件并在表格完成加载数据后将其附加到占位符(否则它会尝试将组件附加到尚不存在的占位符)。
这是我最终得到的结果:
组件.html
<custom-table [data]="tableData"...></custom-table>
组件.ts
@Input() data: Array<CustomDataSource>;
public tableData: Array<CustomTableData> = new Array<CustomTableData>();
...
private mapper(dataSource: CustomDataSource): CustomTableData {
var detailComponentFactory: TableExpandableFactoryColumn = {
componentFactory: this.componentFactoryResolver.resolveComponentFactory(CustomComponent),
properties: {
"phone": dataSource.phone;
}
}
var tableRow : TableExpandableDataRow = {
rowId: dataSource.rowID,
columns: {
"detailComponentFactory": detailComponentFactory,
"textColumn": "test"
}
}
return tableRow;
}
自定义组件.html
<div>
<span>{{phone}}</span>
</div>
自定义组件.ts
@Component({
selector: `[custom-component]`,
templateUrl: 'CustomComponent.html'
})
export class CustomComponent {
@Input() phone: string;
}
自定义表格.html
<mat-table [dataSource]="dataSource">
<ng-container matColumnDef...>
<mat-cell *matCellDef="let row;">
<div [innerHTML]="row.textColumn"></div>
<div id="detail-placeholder-{{row.internalRowId}}" className="cell-placeholder"></div>
</mat-cell>
</ng-container>
</mat-table>
CustomTable.ts(解决方案的核心)
...
@Input() data: any;
public placeholders: { placeholderId: string, factoryColumn: TableExpandableFactoryColumn }[];
public dataSource: MatTableDataSource<any>;
...
constructor(private renderer: Renderer2,
private injector: Injector,
private applicationRef: ApplicationRef) {
}
...
public ngOnChanges(changes: SimpleChanges) {
if (changes['data']) {
// Wait to load table until data input is available
this.setTableDataSource();
this.prepareLoadTableComponents();
}
}
...
private setTableDataSource() {
this.placeholders = [];
this.dataSource = new MatTableDataSource(this.data.map((row) => {
let rowColumns = {};
// process data columns
for (let key in row.columns) {
if ((row.columns[key] as TableExpandableFactoryColumn).componentFactory != undefined) {
// store component data in placeholders to be rendered after the table loads
this.placeholders.push({
placeholderId: "detail-placeholder-" + row.rowId.toString(),
factoryColumn: row.columns[key]
});
rowColumns[key] = "[" + key + "]";
} else {
rowColumns[key] = row.columns[key];
}
}
return rowColumns;
}));
}
private prepareLoadTableComponents() {
let observer = new MutationObserver((mutations, mo) => this.loadTableComponents(mutations, mo, this));
observer.observe(document, {
childList: true,
subtree: true
});
}
private loadTableComponents(mutations: MutationRecord[], mo: MutationObserver, that: any) {
let placeholderExists = document.getElementsByClassName("cell-placeholder"); // make sure angular table has rendered according to data
if (placeholderExists) {
mo.disconnect();
// render all components
if (that.placeholders.length > 0) {
that.placeholders.forEach((placeholder) => {
that.createComponentInstance(placeholder.factoryColumn, placeholder.placeholderId);
});
}
}
setTimeout(() => { mo.disconnect(); }, 5000); // auto-disconnect after 5 seconds
}
private createComponentInstance(factoryColumn: TableExpandableFactoryColumn, placeholderId: string) {
if (document.getElementById(placeholderId)) {
let component = this.createComponentAtElement(factoryColumn.componentFactory, placeholderId);
// map any properties that were passed along
if (factoryColumn.properties) {
for (let key in factoryColumn.properties) {
if (factoryColumn.properties.hasOwnProperty(key)) {
this.renderer.setProperty(component.instance, key, factoryColumn.properties[key]);
}
}
component.changeDetectorRef.detectChanges();
}
}
}
private createComponentAtElement(componentFactory: ComponentFactory<any>, placeholderId: string): ComponentRef<any> {
// create instance of component factory at specified host
let element = document.getElementById(placeholderId);
let componentRef = componentFactory.create(this.injector, [], element);
this.applicationRef.attachView(componentRef.hostView);
return componentRef;
}
...
export class TableExpandableFactoryColumn {
componentFactory: ComponentFactory<any>;
properties: Dictionary<any> | undefined;
}
export class TableExpandableDataRow {
rowId: string;
columns: Dictionary<any>;
}