创建组件实例并传递给另一个呈现为 [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>;
}
从我的组件(例如 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>;
}