【问题标题】:Creating instance of component and passing to another component rendering as [object HTMLelement]创建组件实例并作为 [object HTMLelement] 传递给另一个组件
【发布时间】:2019-04-29 12:19:17
【问题描述】:

从我的组件(例如 Component)中,我正在尝试实例化一个 Angular 组件(例如 CustomComponent),设置一些属性,然后将其发送到一个表(例如 CustomTable)以进行渲染,但我不断收到[object HTMLElement] 而不是表格单元格中呈现的元素。这是我的设置:

组件.html

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

<custom-component #rowDetailTemplate></custom-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;
}

CustomComponent.html

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

CustomComponent.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 中的任何类型的组件/元素,而不仅仅是我的 CustomComponent。

【问题讨论】:

    标签: angular typescript viewchild elementref


    【解决方案1】:

    我没有尝试将组件传递到表格中,而是最终将表格传递给了一个 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;
    }
    

    CustomComponent.html

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-05-03
      • 2019-10-23
      • 1970-01-01
      • 1970-01-01
      • 2021-06-30
      相关资源
      最近更新 更多