创建组件实例并传递给另一个呈现为 [object HTMLelement] 的组件

Creating instance of component and passing to another component rendering as [object HTMLelement]

从我的组件(例如 Component)中,我试图实例化一个 Angular 组件(例如 CustomComponent),设置一些属性,然后将它发送到 table(例如. CustomTable) 进行渲染,但我一直在 [object HTMLElement] 而不是 table 单元格中的渲染元素。这是我的设置:

Component.html

<custom-table [data]="tableData"...></custom-table>

<custom-component #rowDetailTemplate></custom-component>

Component.ts

@Input() data: Array<CustomDataSource>;
@ViewChild('rowDetailTemplate') template: ElementRef;

public tableData: Array<CustomTableData> = new Array<CustomTableData>();

...

private mapper(dataSource: CustomDataSource): CustomTableData {
    var detailComponent = this.template.nativeElement;
    detailComponent.phone = dataSource.phone;

    var tableRow = new CustomTableData();
    tableRow.textColumn = "test";
    tableRow.detailComponent = detailComponent;

    return tableRow;
}

自定义Component.html

<div>
    <span>{{phone}}</span>
</div>

自定义Component.ts

@Component({
    selector: `[custom-component]`,
    templateUrl: 'CustomComponent.html'
})
export class CustomComponent {
    @Input() phone: string;
}

CustomTable.html

<mat-table [dataSource]="dataSource">
    <ng-container matColumnDef...>
        <mat-cell *matCellDef="let element;">
            <div [innerHTML]="element.textColumn"></div>
            <div [innerHTML]="element.detailComponent"></div>
        </mat-cell>
    </ng-container>
</mat-table>

我的文本列呈现良好,只是 custom-component 呈现不正确。

有什么建议吗?

请注意,CustomTable 需要能够接受 detailComponent 中的任何类型的 component/element,而不仅仅是我的 CustomComponent。

我没有尝试将组件传递到 table,而是将组件工厂传递给 table,然后 table 将负责从工厂实例化组件并在 table 完成加载数据后将其附加到占位符(否则它会尝试将组件附加到尚不存在的占位符)。

这是我最终得到的结果:

Component.html

<custom-table [data]="tableData"...></custom-table>

Component.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;
}

自定义Component.html

<div>
    <span>{{phone}}</span>
</div>

自定义Component.ts

@Component({
    selector: `[custom-component]`,
    templateUrl: 'CustomComponent.html'
})
export class CustomComponent {
    @Input() phone: string;
}

CustomTable.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>;
}