如何使用/创建动态模板来编译动态组件与Angular 2.0? [英] How can I use/create dynamic template to compile dynamic Component with Angular 2.0?

查看:1781
本文介绍了如何使用/创建动态模板来编译动态组件与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 创建动态实例组件



以下是代码段(更多此处 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 ++& 运行时组件



这种情况,我们将


  1. 创建模块 PartsModule:NgModule (小件的持有者)

  2. 创建另一个模块 DynamicModule:NgModule 将包含我们的动态组件(和动态引用 PartsModule

  3. 创建动态模板

  4. 创建 li>
  5. 创建新 RuntimeModule:NgModule 。此模块将包含先前创建的组件类型

  6. 调用 JitCompiler.compileModuleAndAllComponentsAsync(runtimeModule)获取 ComponentFactory

  7. 创建 DynamicComponent 作业和 ComponentFactory

  8. @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 我们使用这个简单/天真的构建器。



p>



  // 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. 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.

I found this (offical angular2) document

Angular 2 Synchronous Dynamic Component Creation

And understand that I can use either

  • Kind of dynamic ngIf with ComponentFactoryResolver. 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

  1. create a module PartsModule:NgModule (holder of small pieces)
  2. create another module DynamicModule:NgModule, which will contain our dynamic component (and reference PartsModule dynamically)
  3. create dynamic Template (simple approach)
  4. create new Component type (only if template has changed)
  5. create new RuntimeModule:NgModule. This module will contain the previously created Component type
  6. call JitCompiler.compileModuleAndAllComponentsAsync(runtimeModule) to get ComponentFactory
  7. create an Instance of the DynamicComponent - job of the View Target placeholder and ComponentFactory
  8. assign @Inputs to new instance (switch from INPUT to TEXTAREA editing), consume @Outputs

NgModule

We need an NgModules.

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 the AppModule

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 and Module. 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆