如何使用/创建动态模板来编译动态组件与Angular 2.0? [英] How can I use/create dynamic template to compile dynamic Component with Angular 2.0?
问题描述
我想动态创建模板。这应该用于在运行时构建 ComponentType
,并将(甚至替换)它在主机组件。
直到RC4我使用 ComponentResolver
,但是使用RC5我收到消息:
对于动态编译,不推荐使用ComponentResolver
。使用ComponentFactoryResolver
与@ NgModule / @ Component.entryComponents
或ANALYZE_FOR_ENTRY_COMPONENTS提供程序。 对于仅运行时编译,您还可以使用Compiler.compileComponentSync / Async
。
我发现这个(offical angular2)文档
Angular 2同步动态组件创建
了解我可以使用
- 动态
ngIf
与ComponentFactoryResolver
。如果我将已知组件传递给@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&
...
在另一种情况下,这一个 c> string-editor 替换为文本编辑器
)
< form>
< text-editor
[propertyName] ='code'
[entity] =entity
>< / text-editor&
...
code> editors 的属性类型,跳过某些用户的一些属性...)。也就是说这是一个例子,真正的配置可以生成更多不同和复杂的模板。
模板正在改变,所以我不能使用 ComponentFactoryResolver
并传递现有的...我需要解决方案与编译器
< hr />
AOT和JitCompiler (前RuntimeCompiler)
与AOT(提前编译)?您会得到:
错误:在解析符号值时发生错误。不支持函数调用。考虑使用对导出函数(原始.ts文件中的位置65:17)的引用替换函数或lambda,在... / node_modules/@angular/compiler/src/compiler.d.ts中解析符号COMPILER_PROVIDERS, / p>
请留下您的评论,在这里投票:
AOT能否支持使用COMPILER_PROVIDERS的代码?
EDIT - 与 2.3.0 (2016-12-07)
:要获取以前版本的解决方案,请检查此帖子的历史记录
http://stackoverflow.com/q/34784778/1679310\">在Angular 2中相当于$ compile。我们需要使用 JitCompiler
和 NgModule
。阅读更多关于Angular2中的 NgModule
的详情:
> 工作plunker /示例 (动态模板,动态组件类型,动态模块, JitCompiler
,... in action)
1)创建模板
2)找到 ComponentFactory
缓存 - 转到 7)
3) - 创建组件
4) - 创建模块
5) - 编译模块
6) - return(and cache for later use) ComponentFactory
7) strong>和 ComponentFactory
创建动态实例组件
以下是代码段(更多此处)我们的自定义Builder是返回只是建立/缓存 ComponentFactory
和视图目标占位符消费创建 DynamicComponent
//这里我们得到一个动态内容TEMPLATE === TODO
var template = this.templateBuilder.prepareTemplate(this.entity,useTextarea);
//这里我们得到Factory(只是编译或从缓存)
this.typeBuilder
.createComponentFactory(template)
.then((factory:ComponentFactory< IHaveDynamicData> ;)=>
{
//目标将实例化并注入组件(我们将引用它)
this.componentRef = this
.dynamicComponentTarget
.createComponent(factory);
//让我们注入@Inputs到组件实例
let component = this.componentRef.instance;
component.entity = this。 entity;
// ...
});
这是它 - 简而言之。要获取更多详情,请阅读下文
。
TL& DR
观察一个plunker并返回读取详细信息,
详细说明 - Angular2 RC6 ++& 运行时组件
这种情况,我们将
- 创建模块
PartsModule:NgModule
(小件的持有者)
- 创建另一个模块
DynamicModule:NgModule
将包含我们的动态组件(和动态引用PartsModule
) - 创建动态模板
- 创建
li>
- 创建新
RuntimeModule:NgModule
。此模块将包含先前创建的组件
类型
- 调用
JitCompiler.compileModuleAndAllComponentsAsync(runtimeModule)
获取ComponentFactory
- 创建
DynamicComponent
作业和ComponentFactory
- 将
@Inputs
分配给新实例(从INPUT
切换到TEXTAREA
编辑) consume@Outputs
- 创建新
NgModule
我们需要一个 NgModule
。
虽然我想展示一个非常简单的例子,在这种情况下,我需要三个模块:但我不计入AppModule)。请将此而不是一个简单的代码段作为一个真正可靠的动态组件生成器的基础。
所有小组件都将有一个模块,例如 string-editor
, text-editor
( date-editor
, number-editor
...)
@NgModule({
imports:[
CommonModule,
FormsModule
],
声明:[
DYNAMIC_DIRECTIVES
],
exports:[
DYNAMIC_DIRECTIVES,
CommonModule,
FormsModule
]
})
export class PartsModule {}
其中
DYNAMIC_DIRECTIVES
是可扩展的,旨在包含用于我们的动态组件模板/类型的所有小部件。检查 app / parts / parts.module.ts
第二个是我们的动态内容处理的模块。它将包含托管组件和一些提供程序..这将是单身。因此,我们将以标准方式发布它们 - forRoot()
import {DynamicDetail} from'./detail.view';
import {DynamicTypeBuilder} from'./type.builder';
import {DynamicTemplateBuilder} from'./template.builder';
@NgModule({
imports:[PartsModule],
declarations:[DynamicDetail],
exports:[DynamicDetail],
})
export class DynamicModule {
static forRoot()
{
return {
ngModule:DynamicModule,
providers:[//单个的整个应用程序
DynamicTemplateBuilder,
DynamicTypeBuilder
],
};
}
}
在
AppModule中
forRoot()
最后,我们需要一个adhoc,runtime模块..但是稍后将会创建它作为 DynamicTypeBuilder
作业的一部分。
第四个模块,应用程序模块,是声明编译器提供程序的模块:
...
import {COMPILER_PROVIDERS} from'@ angular / compiler';
import {AppComponent} from'./app.component';
import {DynamicModule} from'./dynamic/dynamic.module';
@NgModule({
imports:[
BrowserModule,
DynamicModule.forRoot()// singletons
],
声明: AppComponent],
providers:[
COMPILER_PROVIDERS // this is an app singleton declaration
],
阅读(阅读)更多关于 NgModule 的信息:
b
在我们的示例中,我们将处理这种实体的细节
entity = {
code:ABC123,
description:此实体的说明
};
要创建模板
,请在此 plunker 我们使用这个简单/天真的构建器。
// plunker - app / dynamic / template.builder.ts
import {Injectable } from@ angular / core;
@Injectable()
export class DynamicTemplateBuilder {
public prepareTemplate(entity:any,useTextarea:boolean){
让属性= Object.keys(entity);
let template =< form>;
let editorName = useTextarea
? text-editor
:string-editor;
properties.forEach((propertyName)=> {
template + =`
< $ {editorName}
[propertyName] ='$ {propertyName }'
[entity] =entity
>< / $ {editorName}>`;
});
return template +< / form>;
}
}
这里的一个技巧是 - 一些已知的属性,例如 实体
。这些属性(-ies)必须是动态组件的一部分,我们将在下面创建它们。
为了使它更容易一些,我们可以使用一个接口来定义属性,我们的模板构建器可以使用它。这将通过我们的动态组件类型实现。
导出接口IHaveDynamicData {
public entity:any;
...
}
A ComponentFactory
builder
这里非常重要的是记住:
我们的组件类型,使用
DynamicTypeBuilder
构建,可能会有所不同,但只有其模板(上面创建)。组件的属性(输入,输出或某些保护)仍然相同。如果我们需要不同的属性,我们应该定义模板和类型构建器的不同组合
触及我们解决方案的核心。构建器将创建 ComponentType
2)创建其 NgModule
3)编译 ComponentFactory
4)缓存以供以后重复使用。
我们需要接收的依赖:
// plunker - app / dynamic / type.builder.ts
import {JitCompiler} from'@ angular / compiler';
@Injectable()
导出类DynamicTypeBuilder {
// wee需要动态组件构建器
构造函数(
protected compiler:JitCompiler
){}
这里是一个代码片段如何获得 ComponentFactory
:
// plunker - app / dynamic / type.builder.ts
// this object is singleton - 所以我们可以使用这个缓存
private _cacheOfFactories:
{[templateKey:string]:ComponentFactory< IHaveDynamicData>} = {};
public createComponentFactory(template:string)
:Promise< ComponentFactory< IHaveDynamicData>> {
let factory = this._cacheOfFactories [template];
if(factory){
console.log(Module and Type are returned from cache)
return new Promise((resolve)=> {
resolve(factory);
});
}
//未知的模板...让我们为它创建一个类型
let type = this.createNewComponent(template);
let module = this.createComponentModule(type);
return new Promise((resolve)=> {
this.compiler
.compileModuleAndAllComponentsAsync(module)
.then((moduleWithFactories)=>
{
factory = _.find(moduleWithFactories.componentFactories
,{componentType:type});
this._cacheOfFactories [template] = factory;
resolve(factory);
});
});
}
/ strong>两个
组件
和模块
。因为如果模板(事实上是所有的真实动态部分)是相同的..我们可以重用
这里有两个方法,它们代表了如何在运行时创建一个装饰的类/类的非常酷的方法。不仅 @Component
,还有 @NgModule
protected createNewComponent(tmpl:string){
@Component({
selector:'dynamic-component',
template:tmpl,
} )
class CustomDynamicComponent implements IHaveDynamicData {
@Input()public entity:any;
};
//这个特定模板的一个组件
return CustomDynamicComponent;
}
protected createComponentModule(componentType:any){
@NgModule({
imports:[
PartsModule,//'text-editor','string -editor'...
],
声明:[
componentType
],
})
类RuntimeComponentModule
{
}
//这个类型的模块
return RuntimeComponentModule;
}
重要提示:
< >
我们的组件动态类型不同,只是通过模板。因此,我们使用这个事实来缓存。这真的很重要。 Angular2也会通过类型缓存这些..如果我们为相同的模板字符串重新创建新的类型...我们将开始产生内存泄漏。
ComponentFactory
由托管组件使用
final piece是一个组件,它托管了我们的动态组件的目标,例如< div#dynamicContentPlaceHolder>< / div>
。我们得到它的引用,并使用 ComponentFactory
创建一个组件。简而言之,这个组件的所有部分(如果需要,打开 plunker here )
让我们先总结import语句:
从'@ angular / core'导入{Component,ComponentRef,ViewChild,ViewContainerRef};
import {AfterViewInit,OnInit,OnDestroy,OnChanges,SimpleChange} from'@ angular / core';
import {IHaveDynamicData,DynamicTypeBuilder} from'./type.builder';
import {DynamicTemplateBuilder} from'./template.builder';
@Component({
selector:'dynamic-detail',
template:`
< div>
选中/取消选中以使用INPUT TEXTAREA:
< input type =checkbox#val(click)=refreshContent(val.checked)/>< hr />
< div#dynamicContentPlaceHolder> div>< hr />
entity:< pre> {{entity | json}}< / pre>
< / div>
`,
} )
export class DynamicDetail实现AfterViewInit,OnChanges,OnDestroy,OnInit
{
// wee需要动态组件构建器
constructor(
protected typeBuilder:DynamicTypeBuilder,
protected templateBuilder:DynamicTemplateBuilder
){}
...
接收,模板和组件构建器。接下来是我们的示例所需的属性(更多在注释中)
< div> with #dynamicContentPlaceHolder
@ViewChild('dynamicContentPlaceHolder',{read:ViewContainerRef})
protected dynamicComponentTarget:ViewContainerRef;
//这将引用动态内容 - 能够销毁它
protected componentRef:ComponentRef< IHaveDynamicData> ;;
//直到ngAfterViewInit,我们不能启动(第一个)处理动态的东西
protected wasViewInitialized = false;
//示例实体...要从其他应用程序部分接收
//这是@Input的一种candiate
protected entity = {
code: ABC123,
description:此实体的描述
};
在这个简单的场景中,我们的托管组件没有任何 @Input
。所以它不必对变化做出反应。但是,尽管有这个事实(并且准备好迎接未来的变化) - 如果组件已经被(第一)启动,我们需要引入一些标志。
最后,我们将使用我们的组件构建器,其只是编译/缓存 ComponentFacotry
。将要求我们的目标占位符实例化该工厂的组件
。
protected refreshContent(useTextarea:boolean = false){
if(this.componentRef){
this.componentRef.destroy );
}
//这里我们得到一个动态内容TEMPLATE === TODO
var template = this.templateBuilder.prepareTemplate(this.entity,useTextarea);
//这里我们得到Factory(只是编译或从缓存)
this.typeBuilder
.createComponentFactory(template)
.then((factory:ComponentFactory< IHaveDynamicData> ;)=>
{
//目标将实例化并注入组件(我们将引用它)
this.componentRef = this
.dynamicComponentTarget
.createComponent(factory);
//让我们注入@Inputs到组件实例
let component = this.componentRef.instance;
component.entity = this。 entity;
// ...
});
}
小额外资讯
此外,我们需要保留对编译模板的引用。只要我们更改它,就能够正确地 destroy()
/ p>
//这是开始处理动态内容的最佳时机
public ngAfterViewInit():void
{
this.wasViewInitialized = true;
this.refreshContent();
}
// wasViewInitialized是一个重要的开关
//当这个组件有自己改变@Input()
// - 那么我们必须等待视图初始化 - 第一次OnChange太快
public ngOnChanges(更改:{[key:string]:SimpleChange}):void
{
if(this.wasViewInitialized){
return;
}
this.refreshContent();
}
public ngOnDestroy(){
if(this.componentRef){
this.componentRef.destroy();
this.componentRef = null;
}
}
done
这是很多。不要忘记销毁任何动态构建的内容(ngOnDestroy)。此外,如果唯一的区别是它们的模板,请务必缓存动态类型
和模块
。
检查所有操作这里
要查看此信息的以前版本(例如RC5相关),请检查历史
I want to dynamically create template. This should be used to build a ComponentType
at Runtime and place (even replace) it somewhere inside of the hosting Component.
Until RC4 I was using ComponentResolver
, but with RC5 I get message:
ComponentResolver
is deprecated for dynamic compilation. UseComponentFactoryResolver
together with@NgModule/@Component.entryComponents
or ANALYZE_FOR_ENTRY_COMPONENTS provider instead. For runtime compile only, you can also useCompiler.compileComponentSync/Async
.
I found this (offical angular2) document
Angular 2 Synchronous Dynamic Component Creation
And understand that I can use either
- Kind of dynamic
ngIf
withComponentFactoryResolver
. If I will pass known components into hosting one inside of@Component({entryComponents: [comp1, comp2], ...})
- I can use.resolveComponentFactory(componentToRender);
- Real runtime compilation, with
Compiler
...
But the question is how to use that Compiler
? The Note above says that I should call: Compiler.compileComponentSync/Async
- so how?
For example. I want to create (based on some configuration conditions) this kind of template for one kind of settings
<form>
<string-editor
[propertyName]="'code'"
[entity]="entity"
></string-editor>
<string-editor
[propertyName]="'description'"
[entity]="entity"
></string-editor>
...
and in another case this one (string-editor
is replaced with text-editor
)
<form>
<text-editor
[propertyName]="'code'"
[entity]="entity"
></text-editor>
...
And so on (different number/date/reference editors
by property types, skipped some properties for some users...). I.e. this is an example, real configuration could generate much more different and complex templates.
The template is changing, so I cannot use ComponentFactoryResolver
and pass existing ones... I need solution with the Compiler
AOT and JitCompiler (former RuntimeCompiler)
Would you like to use this features with AOT (ahead of time compilation)? Are you getting:
Error: Error encountered resolving symbol values statically. Function calls are not supported. Consider replacing the function or lambda with a reference to an exported function (position 65:17 in the original .ts file), resolving symbol COMPILER_PROVIDERS in .../node_modules/@angular/compiler/src/compiler.d.ts,
Please, leave your comment, vote here:
Could/would/will code using COMPILER_PROVIDERS be supported by AOT?
EDIT - related to 2.3.0 (2016-12-07)
NOTE: to get solution for previous version, check the history of this post
Similar topic is discussed here Equivalent of $compile in Angular 2. We need to use JitCompiler
and NgModule
. Read more about NgModule
in Angular2 here:
In a Nutshell
There is a working plunker/example (dynamic template, dynamic component type, dynamic module,JitCompiler
, ... in action)
The principal is:
1) create Template
2) find ComponentFactory
in cache - go to 7)
3) - create Component
4) - create Module
5) - compile Module
6) - return (and cache for later use) ComponentFactory
7) use Target and ComponentFactory
to create an Instance of dynamic Component
Here is a code snippet (more of it here) - Our custom Builder is returning just built/cached ComponentFactory
and the view Target placeholder consume to create an instance of the DynamicComponent
// here we get a TEMPLATE with dynamic content === TODO
var template = this.templateBuilder.prepareTemplate(this.entity, useTextarea);
// here we get Factory (just compiled or from cache)
this.typeBuilder
.createComponentFactory(template)
.then((factory: ComponentFactory<IHaveDynamicData>) =>
{
// Target will instantiate and inject component (we'll keep reference to it)
this.componentRef = this
.dynamicComponentTarget
.createComponent(factory);
// let's inject @Inputs to component instance
let component = this.componentRef.instance;
component.entity = this.entity;
//...
});
This is it - in nutshell it. To get more details.. read below
.
TL&DR
Observe a plunker and come back to read details in case some snippet requires more explanation
.
Detailed explanation - Angular2 RC6++ & runtime components
Below description of this scenario, we will
- create a module
PartsModule:NgModule
(holder of small pieces) - create another module
DynamicModule:NgModule
, which will contain our dynamic component (and referencePartsModule
dynamically) - create dynamic Template (simple approach)
- create new
Component
type (only if template has changed) - create new
RuntimeModule:NgModule
. This module will contain the previously createdComponent
type - call
JitCompiler.compileModuleAndAllComponentsAsync(runtimeModule)
to getComponentFactory
- create an Instance of the
DynamicComponent
- job of the View Target placeholder andComponentFactory
- assign
@Inputs
to new instance (switch fromINPUT
toTEXTAREA
editing), consume@Outputs
NgModule
We need an NgModule
s.
While I would like to show a very simple example, in this case, I would need three modules (in fact 4 - but I do not count the AppModule). Please, take this rather than a simple snippet as a basis for a really solid dynamic component generator.
There will be one module for all small components, e.g. string-editor
, text-editor
(date-editor
, number-editor
...)
@NgModule({
imports: [
CommonModule,
FormsModule
],
declarations: [
DYNAMIC_DIRECTIVES
],
exports: [
DYNAMIC_DIRECTIVES,
CommonModule,
FormsModule
]
})
export class PartsModule { }
Where
DYNAMIC_DIRECTIVES
are extensible and are intended to hold all small parts used for our dynamic Component template/type. Check app/parts/parts.module.ts
The second will be module for our Dynamic stuff handling. It will contain hosting components and some providers.. which will be singletons. Therefor we will publish them standard way - with forRoot()
import { DynamicDetail } from './detail.view';
import { DynamicTypeBuilder } from './type.builder';
import { DynamicTemplateBuilder } from './template.builder';
@NgModule({
imports: [ PartsModule ],
declarations: [ DynamicDetail ],
exports: [ DynamicDetail],
})
export class DynamicModule {
static forRoot()
{
return {
ngModule: DynamicModule,
providers: [ // singletons accross the whole app
DynamicTemplateBuilder,
DynamicTypeBuilder
],
};
}
}
Check the usage of the
forRoot()
in theAppModule
Finally, we will need an adhoc, runtime module.. but that will be created later, as a part of DynamicTypeBuilder
job.
The forth module, application module, is the one who keeps declares compiler providers:
...
import { COMPILER_PROVIDERS } from '@angular/compiler';
import { AppComponent } from './app.component';
import { DynamicModule } from './dynamic/dynamic.module';
@NgModule({
imports: [
BrowserModule,
DynamicModule.forRoot() // singletons
],
declarations: [ AppComponent],
providers: [
COMPILER_PROVIDERS // this is an app singleton declaration
],
Read (do read) much more about NgModule there:
A template builder
In our example we will process detail of this kind of entity
entity = {
code: "ABC123",
description: "A description of this Entity"
};
To create a template
, in this plunker we use this simple/naive builder.
The real solution, a real template builder, is the place where your application can do a lot
// plunker - app/dynamic/template.builder.ts
import {Injectable} from "@angular/core";
@Injectable()
export class DynamicTemplateBuilder {
public prepareTemplate(entity: any, useTextarea: boolean){
let properties = Object.keys(entity);
let template = "<form >";
let editorName = useTextarea
? "text-editor"
: "string-editor";
properties.forEach((propertyName) =>{
template += `
<${editorName}
[propertyName]="'${propertyName}'"
[entity]="entity"
></${editorName}>`;
});
return template + "</form>";
}
}
A trick here is - it builds a template which uses some set of known properties, e.g. entity
. Such property(-ies) must be part of dynamic component, which we will create next.
To make it a bit more easier, we can use an interface to define properties, which our Template builder can use. This will be implemented by our dynamic Component type.
export interface IHaveDynamicData {
public entity: any;
...
}
A ComponentFactory
builder
Very important thing here is to keep in mind:
our component type, build with our
DynamicTypeBuilder
, could differ - but only by its template (created above). Components' properties (inputs, outputs or some protected) are still same. If we need different properties, we should define different combination of Template and Type Builder
So, we are touching the core of our solution. The Builder, will 1) create ComponentType
2) create its NgModule
3) compile ComponentFactory
4) cache it for later reuse.
An dependency we need to receive:
// plunker - app/dynamic/type.builder.ts
import { JitCompiler } from '@angular/compiler';
@Injectable()
export class DynamicTypeBuilder {
// wee need Dynamic component builder
constructor(
protected compiler: JitCompiler
) {}
And here is a snippet how to get a ComponentFactory
:
// plunker - app/dynamic/type.builder.ts
// this object is singleton - so we can use this as a cache
private _cacheOfFactories:
{[templateKey: string]: ComponentFactory<IHaveDynamicData>} = {};
public createComponentFactory(template: string)
: Promise<ComponentFactory<IHaveDynamicData>> {
let factory = this._cacheOfFactories[template];
if (factory) {
console.log("Module and Type are returned from cache")
return new Promise((resolve) => {
resolve(factory);
});
}
// unknown template ... let's create a Type for it
let type = this.createNewComponent(template);
let module = this.createComponentModule(type);
return new Promise((resolve) => {
this.compiler
.compileModuleAndAllComponentsAsync(module)
.then((moduleWithFactories) =>
{
factory = _.find(moduleWithFactories.componentFactories
, { componentType: type });
this._cacheOfFactories[template] = factory;
resolve(factory);
});
});
}
Above we create and cache both
Component
andModule
. Because if the template (in fact the real dynamic part of that all) is the same.. we can reuse
And here are two methods, which represent the really cool way how to create a decorated classes/types in runtime. Not only @Component
but also the @NgModule
protected createNewComponent (tmpl:string) {
@Component({
selector: 'dynamic-component',
template: tmpl,
})
class CustomDynamicComponent implements IHaveDynamicData {
@Input() public entity: any;
};
// a component for this particular template
return CustomDynamicComponent;
}
protected createComponentModule (componentType: any) {
@NgModule({
imports: [
PartsModule, // there are 'text-editor', 'string-editor'...
],
declarations: [
componentType
],
})
class RuntimeComponentModule
{
}
// a module for just this Type
return RuntimeComponentModule;
}
Important:
our component dynamic types differ, but just by template. So we use that fact to cache them. This is really very important. Angular2 will also cache these.. by the type. And if we would recreate for the same template strings new types... we will start to generate memory leaks.
ComponentFactory
used by hosting component
Final piece is a component, which hosts the target for our dynamic component, e.g. <div #dynamicContentPlaceHolder></div>
. We get a reference to it and use ComponentFactory
to create a component. That is in a nutshell, and here are all the pieces of that component (if needed, open plunker here)
Let's firstly summarize import statements:
import {Component, ComponentRef,ViewChild,ViewContainerRef} from '@angular/core';
import {AfterViewInit,OnInit,OnDestroy,OnChanges,SimpleChange} from '@angular/core';
import { IHaveDynamicData, DynamicTypeBuilder } from './type.builder';
import { DynamicTemplateBuilder } from './template.builder';
@Component({
selector: 'dynamic-detail',
template: `
<div>
check/uncheck to use INPUT vs TEXTAREA:
<input type="checkbox" #val (click)="refreshContent(val.checked)" /><hr />
<div #dynamicContentPlaceHolder></div> <hr />
entity: <pre>{{entity | json}}</pre>
</div>
`,
})
export class DynamicDetail implements AfterViewInit, OnChanges, OnDestroy, OnInit
{
// wee need Dynamic component builder
constructor(
protected typeBuilder: DynamicTypeBuilder,
protected templateBuilder: DynamicTemplateBuilder
) {}
...
We just receive, template and component builders. Next are properties which are needed for our example (more in comments)
// reference for a <div> with #dynamicContentPlaceHolder
@ViewChild('dynamicContentPlaceHolder', {read: ViewContainerRef})
protected dynamicComponentTarget: ViewContainerRef;
// this will be reference to dynamic content - to be able to destroy it
protected componentRef: ComponentRef<IHaveDynamicData>;
// until ngAfterViewInit, we cannot start (firstly) to process dynamic stuff
protected wasViewInitialized = false;
// example entity ... to be recieved from other app parts
// this is kind of candiate for @Input
protected entity = {
code: "ABC123",
description: "A description of this Entity"
};
In this simple scenario, our hosting component does not have any @Input
. So it does not have to react to changes. But despite of that fact (and to be ready for coming changes) - we need to introduce some flag if the component was already (firstly) initiated. And only then we can start the magic.
Finally we will use our component builder, and its just compiled/cached ComponentFacotry
. Our Target placeholder will be asked to instantiate the Component
with that factory.
protected refreshContent(useTextarea: boolean = false){
if (this.componentRef) {
this.componentRef.destroy();
}
// here we get a TEMPLATE with dynamic content === TODO
var template = this.templateBuilder.prepareTemplate(this.entity, useTextarea);
// here we get Factory (just compiled or from cache)
this.typeBuilder
.createComponentFactory(template)
.then((factory: ComponentFactory<IHaveDynamicData>) =>
{
// Target will instantiate and inject component (we'll keep reference to it)
this.componentRef = this
.dynamicComponentTarget
.createComponent(factory);
// let's inject @Inputs to component instance
let component = this.componentRef.instance;
component.entity = this.entity;
//...
});
}
small extension
Also, we need to keep a reference to compiled template.. to be able properly destroy()
it, whenever we will change it.
// this is the best moment where to start to process dynamic stuff
public ngAfterViewInit(): void
{
this.wasViewInitialized = true;
this.refreshContent();
}
// wasViewInitialized is an IMPORTANT switch
// when this component would have its own changing @Input()
// - then we have to wait till view is intialized - first OnChange is too soon
public ngOnChanges(changes: {[key: string]: SimpleChange}): void
{
if (this.wasViewInitialized) {
return;
}
this.refreshContent();
}
public ngOnDestroy(){
if (this.componentRef) {
this.componentRef.destroy();
this.componentRef = null;
}
}
done
That is pretty much it. Do not forget to Destroy anything what was built dynamically (ngOnDestroy). Also, be sure to cache dynamic types
and modules
if the only difference is their template.
Check it all in action here
to see previous versions (e.g. RC5 related) of this post, check the history
这篇关于如何使用/创建动态模板来编译动态组件与Angular 2.0?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!