当前位置: 首页 > 知识库问答 >
问题:

如何使用/创建动态模板来编译Angular 2.0的动态组件?

盖弘毅
2023-03-14

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

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

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

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

模板正在更改,因此我无法使用componentfactoryresolver并传递现有的...我需要一个使用编译器的解决方案。

共有1个答案

张敏达
2023-03-14

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

这里讨论了类似的主题,相当于Angular 2中的$compile。我们需要使用jitcompilerngmodule。在此处阅读有关Angular2中NGModule的更多信息:

  • Angular 2 RC5-NGModules、惰性加载和AoT编译

有一个正在工作的柱塞/示例(动态模板、动态组件类型、动态模块、JITCompiler、...in action)

其原则是:
1)创建模板
2)在缓存中查找ComponentFactory-转到7)
3)创建Component
4)创建Module
5)编译Module
6)返回(并缓存供以后使用)ComponentFactory
7)使用目标和ComponentFactory创建动态Component实例

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

就是这样--简而言之。获取更多详细信息..阅读下面的内容

.

观察一个柱塞,如果某个片段需要更多的解释,就回来阅读详细信息

.

在此场景的描述下,我们将

  1. 创建模块partsmodule:ngmodule(小块的持有者)
  2. 创建另一个模块DynamicModule:ngModule,它将包含我们的动态组件(并动态引用PartsModule)
  3. 创建动态模板(简单方法)
  4. 创建新的组件类型(仅当模板已更改时)
  5. 创建新的runtimemodule:ngmodule。此模块将包含先前创建的组件类型
  6. 调用JITCompiler.CompileModuleAndallComponentsAsync(runtimeModule)获取ComponentFactory
  7. 创建视图目标占位符的DynamicComponent作业和ComponentFactory
  8. 的实例
  9. @inputs分配给新实例(从input切换到textarea编辑),使用@outputs

我们需要一个ngmodules。

虽然我想展示一个非常简单的例子,但在本例中,我需要三个模块(实际上是4个--但我不计算AppModule)。请把这个而不是一个简单的片段作为一个真正坚实的动态组件生成器的基础。

所有小组件都有一个模块,例如字符串编辑器,文本编辑器(日期编辑器,数字编辑器...)

@NgModule({
  imports:      [ 
      CommonModule,
      FormsModule
  ],
  declarations: [
      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: [ // singletons accross the whole app
              DynamicTemplateBuilder,
              DynamicTypeBuilder
            ], 
        };
    }
}

检查AppModuleforRoot()的用法

最后,我们需要一个特殊的运行时模块…但它将在以后作为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、惰性加载和AoT编译
  • 角度模块文档

在我们的示例中,我们将处理这类实体的细节

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

为了创建模板,在这个plunker中,我们使用了这个简单/朴素的构建器。

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

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

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

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

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

这里很重要的一点是要记住:

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

所以,我们正在触及我们解决方案的核心。构建器将1)创建componenttype2)创建其ngmodule3)编译componentfactory4)缓存它以供以后重用。

我们需要接收的依赖项:

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

上面我们创建并缓存组件模块。因为如果模板(实际上是所有的真正动态部分)是相同的…我们可以重复使用

这里有两个方法,它们代表了如何在运行时创建修饰的类/类型的非常酷的方法。不仅@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;
}

重要:

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

最后一块是一个组件,它承载动态组件的目标,例如

 类似资料:
  • 问题内容: 是否有任何使用Spring或java替换模板字符串以及值的API。 例如: 其中参数(,)中的形式。 问题答案: 我最喜欢的模板引擎是Apache Velocity 也可以与Spring很好地集成,此处有介绍性文章

  • 问题内容: 如何使用动态模板创建指令? 这就是我现在所拥有的,它可以正确显示标签。但是,我不确定如何在模板上附加其他HTML。或将2个模板合并为1个。 问题答案: 有类似的需求。做这份工作。(不完全确定这是否是“ THE”方法,仍然可以通过角度进行工作) http://jsbin.com/ebuhuv/7/edit-我的探索测试。 需要注意的一件事(以我的示例为例),我的要求之一是,一旦单击“保存

  • 问题内容: 我正在尝试做的是动态创建子组件,这些子组件应该注入到父组件中。例如父组件是包含共享的内容为所有课程如如按钮,和其他东西。根据路线参数,应将作为 子组件的 课程内容动态注入到父组件中。子组件(课程内容)的HTML定义为外部某处的纯字符串,它可以是类似以下内容的对象: 通过在 父组件 模板中进行以下操作可以轻松解决问题。 在每次更改路线参数时,父组件的属性都会更改(内容(新模板)将从对象中

  • 我有多个动态json模板如下 JSON 1 JSON 2 JSON 3 我想在运行时将它们隐藏到JAVA对象中,即在编译时不创建POJO。这可能吗?如果是,如何做到这一点? 我尝试使用Jackson lib将json转换为对象(对象类),但如何创建通用POJO,或者如何使用setter-getter动态创建POJO?

  • 问题内容: 我正在使用Browserify将大型Node.js应用程序编译为一个文件(使用选项和[以避免Express中的问题])。我有一些代码可根据目录中的可用内容动态加载模块: 我的应用程序中出现奇怪的错误,即从已编译文件的加载目录中加载了框架文字文件。我认为这是因为路径设置不正确,并且因为Browserify无法正确处理像这样动态加载。 除了制作静态文件之外,还有没有一种首选的方法可以动态地

  • 问题内容: 我在使用primefaces树实现实现动态树结构时遇到了一些麻烦。在primeface提供的展示柜中,代码的结构如下所示。但是,这是非常静态的。我试图弄清楚如何处理从数据库中获取的数据,在编译时树的深度是未知的。 我以为我可能需要某种递归方法来实现此目的,但我无法完全理解实现的样子。 有什么想法吗? 以下是primefaces的示例代码 问题答案: