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

查看:31
本文介绍了如何使用/创建动态模板来使用 Angular 2.0 编译动态组件?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想动态创建一个模板.这应该用于在运行时构建 ComponentType 并将(甚至替换)它放在托管组件内部的某个位置.

I want to dynamically create a template. This should be used to build a ComponentType at runtime and place (even replace) it somewhere inside of the hosting Component.

在 RC4 之前,我使用的是 ComponentResolver,但使用 RC5 我收到以下消息:

Until RC4 I was using ComponentResolver, but with RC5 I get the following 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.

我找到了这个文档(Angular 2 Synchronous Dynamic Component创作)

并且明白我可以使用任何一个

And understand that I can use either

  • 一种带有 ComponentFactoryResolver 的动态 ngIf.如果我在 @Component({entryComponents: [comp1, comp2], ...}) 中传递已知组件 - 我可以使用 .resolveComponentFactory(componentToRender);
  • 真正的运行时编译,使用Compiler...
  • Kind of dynamic ngIf with ComponentFactoryResolver. If I pass known components inside of @Component({entryComponents: [comp1, comp2], ...}) - I can use .resolveComponentFactory(componentToRender);
  • Real runtime compilation, with Compiler...

但问题是如何使用那个Compiler?上面的注释说我应该调用:Compiler.compileComponentSync/Async - 那么如何?

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

在另一种情况下,这个 (string-editor 被替换为 text-editor)

and in another case this one (string-editor is replaced with text-editor)

<form>
   <text-editor
     [propertyName]="'code'"
     [entity]="entity"
   ></text-editor>
   ...

依此类推(按属性类型不同的编号/日期/参考editors,跳过某些用户的某些属性...).即这是一个示例,实际配置可以生成更多不同和复杂的模板.

And so on (different number/date/reference editors by property types, skipped some properties for some users...). i.e. this is an example, the real configuration could generate much more different and complex templates.

模板正在更改,因此我无法使用 ComponentFactoryResolver 并传递现有模板...我需要使用 Compiler 的解决方案.

The template is changing, so I cannot use ComponentFactoryResolver and pass existing ones... I need a solution with the Compiler.

推荐答案

EDIT - 相关于 2.3.0 (2016-12-07)

<块引用>

注意:要获取以前版本的解决方案,请查看此帖子的历史记录

EDIT - related to 2.3.0 (2016-12-07)

此处讨论了类似的主题等效于 Angular 2 中的 $compile.我们需要使用JitCompilerNgModule.在此处阅读有关 Angular2 中 NgModule 的更多信息:

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

一个工作的plunker/示例(动态模板,动态组件类型,动态模块,JitCompiler,...在行动)

  • Angular 2 RC5 - NgModules, Lazy Loading and AoT compilation

校长是:
1) 创建模板
2) 在缓存中找到 ComponentFactory - 转到 7)
3) - 创建Component
4) - 创建模块
5) - 编译Module
6) - 返回(并缓存以备后用)ComponentFactory
7) 使用 TargetComponentFactory 创建动态 Component

There is a working plunker/example (dynamic template, dynamic component type, dynamic module,JitCompiler, ... in action)

这是一个代码片段(更多内容这里) - 我们的自定义构建器正在返回刚刚构建/缓存的 ComponentFactory 和视图目标占位符用于创建 DynamicComponent

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

.

观察 plunker 并返回阅读详细信息,以防某些片段需要更多解释

.

这种场景的描述下面,我们将

  1. 创建一个模块 PartsModule:NgModule (小块的持有者)
  2. 创建另一个模块DynamicModule:NgModule,它将包含我们的动态组件(并动态引用PartsModule)
  3. 创建动态模板(简单方法)
  4. 创建新的Component 类型(仅当模板已更改时)
  5. 创建新的RuntimeModule:NgModule.该模块将包含之前创建的 Component 类型
  6. 调用JitCompiler.compileModuleAndAllComponentsAsync(runtimeModule)得到ComponentFactory
  7. 创建 DynamicComponent 的实例 - 视图目标占位符和 ComponentFactory 的作业
  8. @Inputs 分配给新实例 (从INPUT 切换到TEXTAREA 编辑),消费@Outputs

Below description of this scenario, we will

Ng 模块

我们需要一个 NgModules.

虽然我想展示一个非常简单的例子,但在这种情况下,我需要三个模块(实际上是 4 个 - 但我不计算 AppModule).请将此而不是简单的片段作为真正可靠的动态组件生成器的基础.

NgModule

We need an NgModules.

将有一个模块用于所有小组件,例如string-editor, text-editor (date-editor, number-editor...)

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

<块引用>

其中 DYNAMIC_DIRECTIVES 是可扩展的,旨在容纳用于我们的动态组件模板/类型的所有小部件.检查 app/parts/parts.module.ts

第二个将是我们的动态材料处理模块.它将包含托管组件和一些提供程序......这将是单例.因此,我们将以标准方式发布它们 - 使用 forRoot()


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

<块引用>

检查AppModule

最后,我们将需要一个临时的运行时模块......但它会在稍后创建,作为 DynamicTypeBuilder 作业的一部分.

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

阅读(阅读)更多关于NgModule的信息:

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

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

要创建模板,在这个plunker中,我们使用这个简单/天真的构建器.

  • Angular 2 RC5 - NgModules, Lazy Loading and AoT compilation
  • Angular Modules documentation

真正的解决方案,真正的模板构建器,是您的应用程序可以做很多事情的地方

To create a template, in this plunker we use this simple/naive builder.

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

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

一个 ComponentFactory 构建器

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

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.

我们的组件类型,使用我们的 DynamicTypeBuilder 构建,可能会有所不同 - 但仅限于它的模板 (在上面创建).组件的属性(输入、输出或某些受保护)仍然相同.如果我们需要不同的属性,我们应该定义模板和类型构建器的不同组合

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.

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

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

这里是一个如何获得 ComponentFactory 的片段:

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:

<块引用>

上面我们创建并缓存ComponentModule.因为如果模板(实际上是所有的动态部分)相同..我们可以重用

// plunker - app/dynamic/type.builder.ts import { JitCompiler } from '@angular/compiler'; @Injectable() export class DynamicTypeBuilder { // wee need Dynamic component builder constructor( protected compiler: JitCompiler ) {}

这里有两个方法,它们代表了如何在运行时创建装饰类/类型的非常酷的方式.不仅是 @Component,还有 @NgModule

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

重要提示:

我们的组件动态类型不同,但只是模板不同.所以我们使用这个事实缓存它们.这真的非常重要.Angular2 也将缓存这些..按类型.如果我们为相同的模板字符串重新创建新类型……我们将开始产生内存泄漏.

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

ComponentFactory 由托管组件使用

最后一部分是一个组件,它承载我们动态组件的目标,例如

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

.我们获得对它的引用并使用 ComponentFactory 创建一个组件.简而言之,这里是该组件的所有部分(如果需要,打开 plunker在这里)

Important:

先总结一下import语句:

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)

`,})导出类 DynamicDetail 实现 AfterViewInit、OnChanges、OnDestroy、OnInit{//我们需要动态组件构建器构造函数(受保护的类型构建器:DynamicTypeBuilder,受保护的模板构建器:DynamicTemplateBuilder) {}...

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)
的引用使用 #dynamicContentPlaceHolder@ViewChild('dynamicContentPlaceHolder', {read: ViewContainerRef})受保护的动态组件目标:ViewContainerRef;//这将是对动态内容的引用 - 能够销毁它受保护的 componentRef: ComponentRef;//直到 ngAfterViewInit,我们才能开始(首先)处理动态内容protected wasViewInitialized = false;//示例实体 ... 要从其他应用程序部分接收//这是@Input 的一种候选受保护的实体 = {代码:ABC123",描述:该实体的描述";};

// 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.所以它不必对变化做出反应.但尽管如此,(并为即将到来的变化做好准备) - 如果组件已经(首先)启动,我们需要引入一些标志.只有这样我们才能开始施展魔法.

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.

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

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

小扩展

此外,我们需要保留对已编译模板的引用.. 以便在我们更改它时能够正确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).另外,如果唯一不同的是模板,请确保缓存动态typesmodules.

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.

这里

要查看此帖子的以前版本(例如 RC5 相关),请查看历史

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

这篇关于如何使用/创建动态模板来使用 Angular 2.0 编译动态组件?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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