在Angular 2中,通过ComponentFactory创建Component似乎不会完全呈现

时间:2016-08-03 21:43:04

标签: angular ag-grid

我试图在Angular 2应用程序中使用ag-grid。因为我使用无限滚动和服务器端过滤,所以我基本上希望网格中的所有列都有自定义过滤器,然后我可以将其传递到实际执行过滤的服务器。虽然ag-grid有一个相对简单的界面来设置这些自定义过滤器,但在ag-grid文档的Angular 2页面上注意到ag-grid使用了Angular 1编译,并且因为Angular 2不支持编译应用程序启动后,网格中的任何自定义组件(自定义过滤器,单元格,行等)都不支持任何Angular 2功能(如双向数据绑定等)。

所以我一直在寻找一种Angular 2方法,将组件动态加载到ag-grid插入其过滤器弹出窗口的DOM元素中。我已经查看了DynamicComponentLoader(已弃用)以及使用ComponentResolver的几种变体。一旦我获得了ComponentResolver,我就可以调用resolveComponent来获取ComponentFactory然后我可以使用@ViewChild获取ViewContainerRef并在该ViewContainerRef上调用createComponent来创建我的新组件。但是,这对网格没有帮助,因为@ViewChild不会像ag-grid那样直接找到一个动态添加到DOM的元素。

或者,一旦我获得ComponentResolver并调用resolveComponent获取ComponentFactory,我就可以在componentFactory上调用create并从我的ViewContainerRef传递注入器,并将我想要组件插入的元素的字符串标记传递给它那个"似乎"工作,但组件未正确呈现。如果我使用DynamicComponentLoader(即组件未按预期呈现),我会得到类似的行为。

是否有一些可接受的方法在DOM中的特定元素中加载Angular 2 Component?

以下是一些代码来说明问题,我基于Angular 2快速入门:

app.component.ts:

import { Component, ViewChild } from '@angular/core';
import { ComponentResolver, ViewContainerRef } from '@angular/core';

import { DynComponent } from './dyn.component';

@Component({
    selector: 'my-app',
    template: `
        <h1>My test Angular 2 App</h1>
        <input type="text"  class="form-control" required
          [(ngModel)]="name" >
          TODO: remove this: {{name}}
        <p> </p>
        <div #insertPoint>
            <button (click)="createDynamicComponent()">Create The Dynamic Component</button>
            Inserting a new component below this.
        </div>`
})
export class AppComponent {
    name: string;
    @ViewChild('insertPoint', {read: ViewContainerRef}) compInsertPoint: ViewContainerRef;

    constructor(private componentResolver: ComponentResolver){}

    createDynamicComponent(){
        console.log("Creating new Component.");

        //You're not supposed to manipulate the DOM like this in Angular 2,
        //but this is what ag-grid is doing
        var newTextSpan = document.createElement('span');
        newTextSpan.innerHTML = `<div id='dynCompDiv'> </div>`;
        this.compInsertPoint.element.nativeElement.appendChild(newTextSpan);

        this.componentResolver.resolveComponent(DynComponent)
            .then(cmpFactory => {
                const ctxInjector = this.compInsertPoint.injector;
                //The below commented out createComponent call will create the
                //component successfully, but I can't figure out how to do the
                //same thing and have the component created within the above
                //<div id='dynCompDiv'>
                // this.compInsertPoint.createComponent(cmpFactory, 0, ctxInjector);

                //This appears to try to create the component, but the component
                //is not expanded correctly (missing text, no two-way data
                //binding)
                cmpFactory.create(ctxInjector, null, '#dynCompDiv');
            })
    }
}

dyn.component.ts

import { Component } from '@angular/core';

@Component({
    selector: 'dyn-app',
    template: `
        <h1>My test Angular 2 App</h1>
        <input type="text"  class="form-control" required
          [(ngModel)]="name" >
          TODO: remove this: {{name}}`
})
export class DynComponent {
    name: string;
}

在app.component中单击按钮后得到的HTML(请注意输入类型中的不匹配=&#34;文本&#34;以及从&#34开始的以下纯文本; TODO:&#34; )

<my-app>
    <h1>My test Angular 2 App</h1>
    <input class="form-control ng-untouched ng-pristine ng-invalid" required="" type="text">
      TODO: remove this: 
    <p> </p>
    <div>
        <button>Create The Dynamic Component</button>
        Inserting a new component below this.
        <span>
            <div id="dynCompDiv">
                <h1>My test Angular 2 App</h1>
                <input class="form-control" required="" type="text">
            </div>
        </span>
    </div>
</my-app>

更新:这是一个使用此示例的Plunker:Angular2 Dynamic Component

更新2 :关于上述评论的更多信息,我使用DynamicComponentLoader获得了类似的行为。如果我正确地阅读了Angular代码,那么DynamicComponentLoader基本上就是我在做同样的事情。它解析组件以获取ComponentFactory,然后在该工厂上使用create方法。因此,类似的行为非常有意义。

更新3 :更新了Plunker以使其再次运行: Updated Plunker

2 个答案:

答案 0 :(得分:1)

我有一个解决这个问题的方法,足以让我再次活动。如上面注释掉的代码中所述,对ViewContainerRef的createComponent调用确实正确地创建了一个组件。唯一的缺点是你只能将它创建为另一个元素的兄弟,而不是另一个元素。由于ag-grid的getGui调用需要返回DOM元素的html,我可以将该组件创建为兄弟,然后在getGui调用中返回该元素。这对我来说似乎不是理想的解决方案,我仍然质疑ComponentFactory上的创建调用是否正常工作,但Angular人员不会将此视为错误:Angular 2 Issue 10523

所以,除了遵循这个解决方法之外,我没有看到任何可口的替代方案。

答案 1 :(得分:0)

从组件工厂创建组件之后,您需要将组件的视图附加到ApplicationRef上,以进行更改检测以在创建的组件上按预期工作。

constructor(private appRef: ApplicationRef){}
...
this.compRef = cmpFactory.create(...);
this.appRef.attachView(this.compRef.hostView);
...
ngOnDestroy() {
   if(this.compRef) {
      this.appRef.detachView(this.compRef.hostView);
   }
}

详细信息见本期https://github.com/angular/angular/issues/10523