angular2 – 我如何使用/创建动态模板来编译动态组件与Angular 2.0?

我想动态创建模板。这应该用于在运行时构建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.

@H_403_16@

我发现这个(offical angular2)文档

Angular 2 Synchronous Dynamic Component Creation

并且明白我可以使用

>使用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和传递现有的…我需要解决方案与编译器

AOT和JitCompiler(前RuntimeCompiler)

您要使用此功能与AOT(提前编译)吗?你得到:

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,

@H_403_16@

请留下您的评论,在这里投票:

Could/would/will code using COMPILER_PROVIDERS be supported by AOT?

编辑 – 相关 2.3.0(2016-12-07)

NOTE: to get solution for prevIoUs version,check the history of this post

@H_403_16@

类似的主题在这里讨论Equivalent of $compile in Angular 2.我们需要使用JitCompiler和NgModule。阅读更多关于NgModule在Angular2这里:

> Angular 2 RC5 – NgModules,Lazy Loading and AoT compilation

简而言之

a working plunker/example(动态模板,动态组件类型,动态模块,JitCompiler,…)

主体是:
1)创建模板
2)在缓存中找到ComponentFactory – 转到7)
3) – 创建组件
4) – 创建模块
5) – 编译模块
6) – return(和缓存供以后使用)ComponentFactory
7)使用Target和ComponentFactory创建一个动态组件实例

这里是一个代码片段(更多的是here) – 我们的自定义Builder是返回刚构建/缓存的ComponentFactory和视图目标占位符消费来创建一个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;
        //...
    });

这是它 – 简而言之。获取更多细节..阅读下面

TL& DR

观察一个plunker,回来阅读细节,以防一些片段需要更多的解释

详细说明 – Angular2 RC6&运行时组件

下面的this scenario的描述,我们会

>创建一个模块PartModule:NgModule(小件的持有者)
>创建另一个模块DynamicModule:NgModule,它将包含我们的动态组件(和动态引用PartsModule)
>创建动态模板(简单方法)
>创建新组件类型(仅当模板已更改时)
> create new RuntimeModule:NgModule。此模块将包含先前创建的组件类型
>调用JitCompiler.compileModuleAndAllComponentsAsync(runtimeModule)来获取ComponentFactory
>创建一个DynamicComponent的实例 – View Target占位符和ComponentFactory的作业
>将@Inputs分配给新实例(从INPUT切换到TEXTAREA编辑),使用@Outputs

NgModule

我们需要一个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.

@H_403_16@

将有一个模块用于所有小组件,例如。字符串编辑器,文本编辑器(日期编辑器,数字编辑器…)

@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 07006

@H_403_16@

第二个将是我们的动态东西处理的模块。它将包含托管组件和一些提供程序..这将是单身。因此我们将发布它们的标准方式 – with for()

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

@H_403_16@

最后,我们将需要一个adhoc,运行时模块,但是稍后将创建它,作为DynamicTypeBuilder作业的一部分。

第四个模块,应用程序模块,是声明编译器提供者的人:

...
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
  ],

阅读(阅读)更多关于NgModule有:

> Angular 2 RC5 – NgModules,Lazy Loading and AoT compilation
> Angular Modules documentation

模板构建器

在我们的例子中,我们将处理这种实体的细节

entity = { 
    code: "ABC123",description: "A description of this Entity" 
};

要创建一个模板,在这个plunker我们使用这个简单/天真的生成器。

The real solution,a real template builder,is the place where your application can do a lot

@H_403_16@
// 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>";
    }
}

这里的一个技巧是 – 它构建一个模板,使用一些已知的属性,例如。实体。这些属性(-ies)必须是动态组件的一部分,我们将在下面创建它们。

为了使它更容易一些,我们可以使用一个接口来定义属性,我们的模板构建器可以使用它。这将由我们的动态组件类型实现。

export interface IHaveDynamicData { 
    public entity: any;
    ...
}

ComponentFactory生成

这里非常重要的是要记住:

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

@H_403_16@

因此,我们正在触及我们的解决方案的核心。 Builder,将1)创建ComponentType 2)创建它的NgModule 3)编译ComponentFactory 4)缓存它以供以后重用。

我们需要接收的依赖:

// plunker - app/dynamic/type.builder.ts
import { JitCompiler } from '@angular/compiler';

@Injectable()
export class DynamicTypeBuilder {

  // wee need Dynamic component builder
  constructor(
    protected compiler: JitCompiler
  ) {}

这里是一个片段如何获得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

@H_403_16@

这里有两个方法,它们代表了如何在运行时创建一个装饰的类/类的真正酷的方法。不仅@Component而且@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;
}

重要:

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.

@H_403_16@

主机组件使用ComponentFactory

最后一块是一个组件,它托管了我们的动态组件的目标,例如。 < div#dynamicContentPlaceHolder>< / div&gt ;.我们得到它的引用,并使用ComponentFactory创建一个组件。简而言之,这个组件的所有部分(如果需要,打开plunker here)

让我们先总结import语句:

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,OnInit
{ 
    // wee need Dynamic component builder
    constructor(
        protected typeBuilder: DynamicTypeBuilder,protected templateBuilder: DynamicTemplateBuilder
    ) {}
    ...

我们只是接收,模板和组件构建器。接下来是我们的示例需要的属性(更多在评论中)

// 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" 
  };

在这个简单的场景中,我们的托管组件没有任何@Input。所以它不必对变化做出反应。但是尽管有这个事实(并且准备好迎接未来的变化),如果组件已经(首先)启动,我们需要引入一些标志。只有这样,我们才能开始魔法。

最后,我们将使用我们的组件构建器,以及它刚刚编译/缓存的ComponentFacotry。我们的目标占位符将被要求与该工厂实例化组件。

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;
        //...
    });
}

小延伸

此外,我们需要保持一个引用编译模板..能够正确地destroy()它,每当我们将改变它。

// 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;
  }
}

完成

这是很多。不要忘记破坏什么是动态构建的(ngOnDestroy)。此外,如果唯一的区别是它们的模板,请确保缓存动态类型和模块。

检查所有在行动here

to see prevIoUs versions (e.g. RC5 related) of this post,check the 070012

@H_403_16@

相关文章

AngularJS 是一个JavaScript 框架。它可通过 注:建议把脚本放在 元素的底部。这会提高网页加载速度,因...
angluarjs中页面初始化的时候会出现语法{{}}在页面中问题,也即是页面闪烁问题。出现这个的原因是:由于...
AngularJS 通过被称为指令的新属性来扩展 HTML。AngularJS 指令AngularJS 指令是扩展的 HTML 属性,带有...
AngularJS 使用表达式把数据绑定到 HTML。AngularJS 表达式AngularJS 表达式写在双大括号内:{{ expres...
ng-repeat 指令可以完美的显示表格。在表格中显示数据 {{ x.Name }} {{ x.Country }} 使用 CSS 样式为了...
$http是 AngularJS 中的一个核心服务,用于读取远程服务器的数据。读取 JSON 文件下是存储在web服务器上...