我想动态创建一个模板。这应该用于在运行时构建ComponentType,并将其放置(甚至替换)到宿主组件内部的某个位置。
直到RC4我使用ComponentResolver,但与RC5我得到以下消息:
ComponentResolver is deprecated for dynamic compilation.
Use ComponentFactoryResolver together with @NgModule/@Component.entryComponents or ANALYZE_FOR_ENTRY_COMPONENTS provider instead.
For runtime compile only, you can also use Compiler.compileComponentSync/Async.
我找到了这个文档(Angular 2同步动态组件创建)
你要明白我可以用任何一种
一种带有ComponentFactoryResolver的动态ngIf。如果我在@Component({entryComponents: [comp1, comp2],…})内部传递已知的组件-我可以使用.resolveComponentFactory(componentToRender);
真正的运行时编译,使用编译器…
但问题是如何使用编译器?上面的说明说,我应该调用:Compiler.compileComponentSync/Async -那么如何?
为例。我想为一种设置创建(基于一些配置条件)这种模板
<form>
<string-editor
[propertyName]="'code'"
[entity]="entity"
></string-editor>
<string-editor
[propertyName]="'description'"
[entity]="entity"
></string-editor>
...
在另一种情况下,这个(字符串编辑器被文本编辑器取代)
<form>
<text-editor
[propertyName]="'code'"
[entity]="entity"
></text-editor>
...
等等(根据属性类型设置不同的数字/日期/引用编辑器,为某些用户跳过一些属性……)例如,这是一个例子,实际的配置可以生成更多不同和复杂的模板。
模板正在改变,所以我不能使用ComponentFactoryResolver和传递现有的…我需要一个解决方案与编译器。
我一定是迟到了,这里没有一个解决方案对我有帮助——太乱了,感觉像是一个太多的变通办法。
我最终使用了Angular 4.0.0-beta。6 ngComponentOutlet。
这给了我最短、最简单的解决方案,所有这些都写在动态组件的文件中。
下面是一个简单的例子,它只是接收文本并将其放在模板中,但显然你可以根据自己的需要进行更改:
import {
Component, OnInit, Input, NgModule, NgModuleFactory, Compiler
} from '@angular/core';
@Component({
selector: 'my-component',
template: `<ng-container *ngComponentOutlet="dynamicComponent;
ngModuleFactory: dynamicModule;"></ng-container>`,
styleUrls: ['my.component.css']
})
export class MyComponent implements OnInit {
dynamicComponent;
dynamicModule: NgModuleFactory<any>;
@Input()
text: string;
constructor(private compiler: Compiler) {
}
ngOnInit() {
this.dynamicComponent = this.createNewComponent(this.text);
this.dynamicModule = this.compiler.compileModuleSync(this.createComponentModule(this.dynamicComponent));
}
protected createComponentModule (componentType: any) {
@NgModule({
imports: [],
declarations: [
componentType
],
entryComponents: [componentType]
})
class RuntimeComponentModule
{
}
// a module for just this Type
return RuntimeComponentModule;
}
protected createNewComponent (text:string) {
let template = `dynamically created template with text: ${text}`;
@Component({
selector: 'dynamic-component',
template: template
})
class DynamicComponent implements OnInit{
text: any;
ngOnInit() {
this.text = text;
}
}
return DynamicComponent;
}
}
简短说明:
My-component—动态组件在其中呈现的组件
DynamicComponent -要动态构建的组件,它在my-component内部呈现
别忘了把所有的angular库升级到^ angular 4.0.0
希望这对你有所帮助,祝你好运!
更新
同样适用于angular 5。
2019年6月答案
好消息!看起来@angular/cdk包现在对门户提供了一流的支持!
在撰写本文时,我并没有发现上面的官方文档有什么特别的帮助(特别是在向动态组件发送数据和从动态组件接收事件方面)。总之,你需要:
步骤1)更新AppModule
从@angular/cdk/portal包中导入PortalModule,在entryComponents中注册你的动态组件
@NgModule({
declarations: [ ..., AppComponent, MyDynamicComponent, ... ]
imports: [ ..., PortalModule, ... ],
entryComponents: [ ..., MyDynamicComponent, ... ]
})
export class AppModule { }
步骤2。选项A:如果你不需要向动态组件传递数据和从动态组件接收事件:
@Component({
selector: 'my-app',
template: `
<button (click)="onClickAddChild()">Click to add child component</button>
<ng-template [cdkPortalOutlet]="myPortal"></ng-template>
`
})
export class AppComponent {
myPortal: ComponentPortal<any>;
onClickAddChild() {
this.myPortal = new ComponentPortal(MyDynamicComponent);
}
}
@Component({
selector: 'app-child',
template: `<p>I am a child.</p>`
})
export class MyDynamicComponent{
}
看看它的实际应用
步骤2。选项B:如果你确实需要向动态组件传递数据并从动态组件接收事件:
// A bit of boilerplate here. Recommend putting this function in a utils
// file in order to keep your component code a little cleaner.
function createDomPortalHost(elRef: ElementRef, injector: Injector) {
return new DomPortalHost(
elRef.nativeElement,
injector.get(ComponentFactoryResolver),
injector.get(ApplicationRef),
injector
);
}
@Component({
selector: 'my-app',
template: `
<button (click)="onClickAddChild()">Click to add random child component</button>
<div #portalHost></div>
`
})
export class AppComponent {
portalHost: DomPortalHost;
@ViewChild('portalHost') elRef: ElementRef;
constructor(readonly injector: Injector) {
}
ngOnInit() {
this.portalHost = createDomPortalHost(this.elRef, this.injector);
}
onClickAddChild() {
const myPortal = new ComponentPortal(MyDynamicComponent);
const componentRef = this.portalHost.attach(myPortal);
setTimeout(() => componentRef.instance.myInput
= '> This is data passed from AppComponent <', 1000);
// ... if we had an output called 'myOutput' in a child component,
// this is how we would receive events...
// this.componentRef.instance.myOutput.subscribe(() => ...);
}
}
@Component({
selector: 'app-child',
template: `<p>I am a child. <strong>{{myInput}}</strong></p>`
})
export class MyDynamicComponent {
@Input() myInput = '';
}
看看它的实际应用