Angular 2、通过ComponentFactory创建Component好像渲染不完全
In Angular 2, creating a Component through ComponentFactory does not seem to render completely
我正在尝试在 Angular 2 应用程序中使用 ag-grid。因为我使用的是无限滚动和服务器端过滤,所以我基本上希望网格中的所有列都具有自定义过滤器,然后我可以将这些过滤器传递给实际执行过滤的服务器。虽然 ag-grid 有一个相对直接的界面来设置这些自定义过滤器,但在 ag-grid 文档的 Angular 2 页上指出,ag-grid 使用了 Angular 1 编译和由于 Angular 2 不支持应用程序启动后编译,因此网格中的自定义组件(自定义过滤器、单元格、行等)的 none 将支持任何 Angular 2功能(如双向数据绑定等)。
所以我一直在寻找一种 Angular 2 方法来将组件动态加载到 ag-grid 插入其过滤器弹出窗口的 DOM 元素中 window .我查看了 DynamicComponentLoader(已弃用)和使用 ComponentResolver 的几个变体。获得 ComponentResolver 后,我可以调用 resolveComponent 来获取 ComponentFactory,然后我可以使用 @ViewChild 来获取 ViewContainerRef 并在该 ViewContainerRef 上调用 createComponent 来创建我的新组件。但是,这对网格没有帮助,因为@ViewChild 不会像 ag-grid 那样直接找到动态添加到 DOM 的元素。
或者,一旦我获得了 ComponentResolver 并调用 resolveComponent 来获取 ComponentFactory,我就可以在 componentFactory 上调用 create 并将它传递给来自我的 ViewContainerRef 的注入器和我希望将组件插入其中的元素的字符串标记"seems to" 有效,但组件未正确呈现。如果我使用 DynamicComponentLoader(即组件未按预期呈现),我会得到类似的行为。
是否有一些公认的方法可以在 DOM 中的特定元素中加载 Angular 2 组件?
下面是一些说明问题的代码,我基于 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(注意输入类型不匹配="text"和以下从"TODO:"开始的纯文本)
<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
我有一个解决这个问题的方法,足以让我再次行动起来。如上面注释掉的代码中所述,对 ViewContainerRef 的 createComponent 调用确实正确地创建了一个组件。唯一的缺点是您只能将其创建为另一个元素的同级元素,而不能在另一个元素中创建。由于 ag-grid 的 getGui 调用需要 DOM 元素的 return of html,我可以将组件创建为同级元素,然后 return getGui 调用中的元素。这对我来说似乎不是理想的解决方案,我仍然质疑 ComponentFactory 上的创建调用是否正常工作,但 Angular 的人不认为这是一个错误:Angular 2 Issue 10523
因此,除了遵循此变通办法外,我看不到任何其他可行的选择。
从组件工厂创建组件后,您需要将组件的视图附加到 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);
}
}
我正在尝试在 Angular 2 应用程序中使用 ag-grid。因为我使用的是无限滚动和服务器端过滤,所以我基本上希望网格中的所有列都具有自定义过滤器,然后我可以将这些过滤器传递给实际执行过滤的服务器。虽然 ag-grid 有一个相对直接的界面来设置这些自定义过滤器,但在 ag-grid 文档的 Angular 2 页上指出,ag-grid 使用了 Angular 1 编译和由于 Angular 2 不支持应用程序启动后编译,因此网格中的自定义组件(自定义过滤器、单元格、行等)的 none 将支持任何 Angular 2功能(如双向数据绑定等)。
所以我一直在寻找一种 Angular 2 方法来将组件动态加载到 ag-grid 插入其过滤器弹出窗口的 DOM 元素中 window .我查看了 DynamicComponentLoader(已弃用)和使用 ComponentResolver 的几个变体。获得 ComponentResolver 后,我可以调用 resolveComponent 来获取 ComponentFactory,然后我可以使用 @ViewChild 来获取 ViewContainerRef 并在该 ViewContainerRef 上调用 createComponent 来创建我的新组件。但是,这对网格没有帮助,因为@ViewChild 不会像 ag-grid 那样直接找到动态添加到 DOM 的元素。
或者,一旦我获得了 ComponentResolver 并调用 resolveComponent 来获取 ComponentFactory,我就可以在 componentFactory 上调用 create 并将它传递给来自我的 ViewContainerRef 的注入器和我希望将组件插入其中的元素的字符串标记"seems to" 有效,但组件未正确呈现。如果我使用 DynamicComponentLoader(即组件未按预期呈现),我会得到类似的行为。
是否有一些公认的方法可以在 DOM 中的特定元素中加载 Angular 2 组件?
下面是一些说明问题的代码,我基于 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(注意输入类型不匹配="text"和以下从"TODO:"开始的纯文本)
<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
我有一个解决这个问题的方法,足以让我再次行动起来。如上面注释掉的代码中所述,对 ViewContainerRef 的 createComponent 调用确实正确地创建了一个组件。唯一的缺点是您只能将其创建为另一个元素的同级元素,而不能在另一个元素中创建。由于 ag-grid 的 getGui 调用需要 DOM 元素的 return of html,我可以将组件创建为同级元素,然后 return getGui 调用中的元素。这对我来说似乎不是理想的解决方案,我仍然质疑 ComponentFactory 上的创建调用是否正常工作,但 Angular 的人不认为这是一个错误:Angular 2 Issue 10523
因此,除了遵循此变通办法外,我看不到任何其他可行的选择。
从组件工厂创建组件后,您需要将组件的视图附加到 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);
}
}