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

在另一种情况下,这个(字符串编辑器被文本编辑器取代)

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

等等(根据属性类型设置不同的数字/日期/引用编辑器,为某些用户跳过一些属性……)例如,这是一个例子,实际的配置可以生成更多不同和复杂的模板。

模板正在改变,所以我不能使用ComponentFactoryResolver和传递现有的…我需要一个解决方案与编译器。


当前回答

我有一个简单的例子来展示如何做angular 2 rc6动态组件。

比方说,你有一个动态html template = template1,想要动态加载,首先包装成组件

@Component({template: template1})
class DynamicComponent {}

这里template1作为html,可能包含ng2组件

在rc6中,必须使用@NgModule来包装这个组件。@NgModule,就像anglarJS 1中的module一样,它解耦了ng2应用程序的不同部分,因此:

@Component({
  template: template1,

})
class DynamicComponent {

}
@NgModule({
  imports: [BrowserModule,RouterModule],
  declarations: [DynamicComponent]
})
class DynamicModule { }

(这里导入RouterModule,在我的例子中,有一些路由组件在我的html中,你可以在后面看到)

现在你可以这样编译DynamicModule: this.compiler.compileModuleAndAllComponentsAsync (DynamicModule) ( factory => factory. componentfactories .find(x => x.p omenttype === DynamicComponent)

我们需要把上面的内容放到app. module .ts中来加载,请查看我的app.moudle.ts。 更多详细信息请查看: https://github.com/Longfld/DynamicalRouter/blob/master/app/MyRouterLink.ts和app.moudle.ts

并查看演示:http://plnkr.co/edit/1fdAYP5PAbiHdJfTKgWo?p=preview

其他回答

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

注意:要获得以前版本的解决方案,请检查这篇文章的历史

这里讨论了类似的主题,相当于Angular 2中的$compile。我们需要使用JitCompiler和NgModule。在这里阅读更多关于Angular2中的NgModule:

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

简而言之

有一个工作的活塞/例子(动态模板,动态组件类型,动态模块,JitCompiler,…在行动)

其原理是: 1)创建模板 2)在缓存中找到ComponentFactory -转到7) 3) -创建组件 4) -创建模块 5) -编译模块 6) -返回(和缓存供以后使用)ComponentFactory 7)使用Target和ComponentFactory创建一个动态组件的实例

下面是一个代码片段(更多的代码在这里)-我们的自定义生成器返回刚刚构建/缓存的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

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

.

详细说明- Angular2 rc6++ &运行时组件

下面对这个场景进行描述

create a module PartsModule:NgModule (holder of small pieces) create another module DynamicModule:NgModule, which will contain our dynamic component (and reference PartsModule 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 created Component type call JitCompiler.compileModuleAndAllComponentsAsync(runtimeModule) to get ComponentFactory create an Instance of the DynamicComponent - job of the View Target placeholder and ComponentFactory assign @Inputs to new instance (switch from INPUT to TEXTAREA editing), consume @Outputs

NgModule

我们需要一个ngmodule。

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

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

@NgModule({
  imports:      [ 
      CommonModule,
      FormsModule
  ],
  declarations: [
      DYNAMIC_DIRECTIVES
  ],
  exports: [
      DYNAMIC_DIRECTIVES,
      CommonModule,
      FormsModule
  ]
})
export class PartsModule { }

其中DYNAMIC_DIRECTIVES是可扩展的,用于保存动态组件模板/类型所使用的所有小部件。检查应用程序/部分/ 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
            ], 
        };
    }
}

检查AppModule中forRoot()的使用情况

最后,我们将需要一个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、惰性加载和AoT编译 Angular模块文档

模板构建器

在我们的示例中,我们将处理这种实体的详细信息

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

为了创建一个模板,在这个活塞中,我们使用这个简单/幼稚的构建器。

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

// 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)必须是我们接下来创建的动态组件的一部分。

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

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

ComponentFactory构建器

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

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

所以,我们触及了解的核心。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);
            });
    });
}

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

这里有两个方法,它们代表了如何在运行时创建装饰类/类型的非常酷的方式。不仅是@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也会缓存这些..按类型。如果我们要为相同的模板字符串重新创建新类型…我们将开始产生内存泄漏。

宿主组件使用的ComponentFactory

最后一部分是一个组件,它承载动态组件的目标,例如<div #dynamicContentPlaceHolder></div>。我们获取它的引用,并使用ComponentFactory创建一个组件。简而言之,这里是该组件的所有部件(如果需要,在这里打开活塞)

让我们首先总结一下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, OnChanges, OnDestroy, 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。所以它不必对变化做出反应。但是尽管如此(并且为即将到来的更改做好准备),如果组件已经(第一次)初始化,我们需要引入一些标志。只有这样,我们才能开始施展魔法。

最后,我们将使用我们的组件构建器,它只是编译/缓存了componentfactory。我们的目标占位符将被要求用该工厂实例化组件。

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

小的扩展

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

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

差不多就是这样。不要忘记销毁任何动态构建的东西(ngOnDestroy)。此外,如果动态类型和模块的唯一区别是它们的模板,请确保缓存它们。

在这里查看所有的操作

要查看这篇文章的以前版本(例如RC5相关),请查看历史记录

EDIT (26/08/2017): The solution below works well with Angular2 and 4. I've updated it to contain a template variable and click handler and tested it with Angular 4.3. For Angular4, ngComponentOutlet as described in Ophir's answer is a much better solution. But right now it does not support inputs & outputs yet. If [this PR](https://github.com/angular/angular/pull/15362] is accepted, it would be possible through the component instance returned by the create event. ng-dynamic-component may be the best and simplest solution altogether, but I haven't tested that yet.

@Long Field的答案是正确的!下面是另一个(同步)例子:

import {Compiler, Component, NgModule, OnInit, ViewChild,
  ViewContainerRef} from '@angular/core'
import {BrowserModule} from '@angular/platform-browser'

@Component({
  selector: 'my-app',
  template: `<h1>Dynamic template:</h1>
             <div #container></div>`
})
export class App implements OnInit {
  @ViewChild('container', { read: ViewContainerRef }) container: ViewContainerRef;

  constructor(private compiler: Compiler) {}

  ngOnInit() {
    this.addComponent(
      `<h4 (click)="increaseCounter()">
        Click to increase: {{counter}}
      `enter code here` </h4>`,
      {
        counter: 1,
        increaseCounter: function () {
          this.counter++;
        }
      }
    );
  }

  private addComponent(template: string, properties?: any = {}) {
    @Component({template})
    class TemplateComponent {}

    @NgModule({declarations: [TemplateComponent]})
    class TemplateModule {}

    const mod = this.compiler.compileModuleAndAllComponentsSync(TemplateModule);
    const factory = mod.componentFactories.find((comp) =>
      comp.componentType === TemplateComponent
    );
    const component = this.container.createComponent(factory);
    Object.assign(component.instance, properties);
    // If properties are changed at a later stage, the change detection
    // may need to be triggered manually:
    // component.changeDetectorRef.detectChanges();
  }
}

@NgModule({
  imports: [ BrowserModule ],
  declarations: [ App ],
  bootstrap: [ App ]
})
export class AppModule {}

请访问http://plnkr.co/edit/fdP9Oc。

我想在Radim的这篇非常出色的文章上添加一些细节。

我采用了这个解决方案并研究了一段时间,很快就遇到了一些限制。我只列出这些,然后给出答案。

首先,我无法渲染动态细节在一个 dynamic-detail(基本上是将动态ui嵌套在彼此内部)。 下一个问题是我想在里面渲染一个动态细节 解决方案中提供的部件之一。这是 初始解也是不可能的。 最后,它不可能在动态部分使用模板url,如字符串编辑器。

我在这篇文章的基础上提出了另一个问题,关于如何实现这些限制,可以在这里找到:

在angar2中递归动态模板编译

如果您遇到与我相同的问题,我将概述这些限制的答案,因为这使解决方案更加灵活。这将是了不起的有最初的活塞更新,以及。

为了在彼此内部嵌套dynamic-detail,你需要在type.builder.ts的import语句中添加DynamicModule.forRoot()

protected createComponentModule (componentType: any) {
    @NgModule({
    imports: [
        PartsModule, 
        DynamicModule.forRoot() //this line here
    ],
    declarations: [
        componentType
    ],
    })
    class RuntimeComponentModule
    {
    }
    // a module for just this Type
    return RuntimeComponentModule;
}

除此之外,在字符串编辑器或文本编辑器的一个部分中使用<dynamic-detail>是不可能的。

要启用它,您需要更改parts.module.ts和dynamic.module.ts

你需要在DYNAMIC_DIRECTIVES中添加DynamicDetail

export const DYNAMIC_DIRECTIVES = [
   forwardRef(() => StringEditor),
   forwardRef(() => TextEditor),
   DynamicDetail
];

同样在dynamic.module.ts中,你必须删除dynamicDetail,因为它们现在是部件的一部分

@NgModule({
   imports:      [ PartsModule ],
   exports:      [ PartsModule],
})

一个工作的修改活塞可以在这里找到:http://plnkr.co/edit/UYnQHF?p=preview(我没有解决这个问题,我只是信使:-D)

最后,不可能在动态组件上创建的部件中使用templateurls。解决方案(或变通方案)。我不确定这是一个angular错误还是框架的错误使用)是在构造函数中创建一个编译器,而不是注入它。

    private _compiler;

    constructor(protected compiler: RuntimeCompiler) {
        const compilerFactory : CompilerFactory =
        platformBrowserDynamic().injector.get(CompilerFactory);
        this._compiler = compilerFactory.createCompiler([]);
    }

然后使用_compiler进行编译,然后也启用templateUrls。

return new Promise((resolve) => {
        this._compiler
            .compileModuleAndAllComponentsAsync(module)
            .then((moduleWithFactories) =>
            {
                let _ = window["_"];
                factory = _.find(moduleWithFactories.componentFactories, { componentType: type });

                this._cacheOfFactories[template] = factory;

                resolve(factory);
            });
    });

希望这能帮助到其他人!

致以最亲切的问候 Morten

我一定是迟到了,这里没有一个解决方案对我有帮助——太乱了,感觉像是一个太多的变通办法。

我最终使用了Angular 4.0.0-beta。6 ngComponentOutlet。

这给了我最短、最简单的解决方案,所有这些都写在动态组件的文件中。

下面是一个简单的例子,它只是接收文本并将其放在模板中,但显然你可以根据自己的需要进行更改:

import {
  Component, OnInit, Input, NgModule, NgModuleFactory, Compiler
} from '@angular/core';

@Component({
  selector: 'my-component',
  template: `<ng-container *ngComponentOutlet="dynamicComponent;
                            ngModuleFactory: dynamicModule;"></ng-container>`,
  styleUrls: ['my.component.css']
})
export class MyComponent implements OnInit {
  dynamicComponent;
  dynamicModule: NgModuleFactory<any>;

  @Input()
  text: string;

  constructor(private compiler: Compiler) {
  }

  ngOnInit() {
    this.dynamicComponent = this.createNewComponent(this.text);
    this.dynamicModule = this.compiler.compileModuleSync(this.createComponentModule(this.dynamicComponent));
  }

  protected createComponentModule (componentType: any) {
    @NgModule({
      imports: [],
      declarations: [
        componentType
      ],
      entryComponents: [componentType]
    })
    class RuntimeComponentModule
    {
    }
    // a module for just this Type
    return RuntimeComponentModule;
  }

  protected createNewComponent (text:string) {
    let template = `dynamically created template with text: ${text}`;

    @Component({
      selector: 'dynamic-component',
      template: template
    })
    class DynamicComponent implements OnInit{
       text: any;

       ngOnInit() {
       this.text = text;
       }
    }
    return DynamicComponent;
  }
}

简短说明: My-component—动态组件在其中呈现的组件 DynamicComponent -要动态构建的组件,它在my-component内部呈现

别忘了把所有的angular库升级到^ angular 4.0.0

希望这对你有所帮助,祝你好运!

更新

同样适用于angular 5。

这是从服务器生成的动态表单控件的示例。

https://stackblitz.com/edit/angular-t3mmg6

这个例子是在添加组件中的动态表单控件(这是你可以从服务器获取表单控件的地方)。如果你看到addcomponent method,你可以看到表单控件。在这个例子中,我没有使用角材料,但它工作(我使用@ work)。这是angular 6的目标,但在之前的所有版本中都有效。

需要为AngularVersion 5及以上版本添加JITComplierFactory。

谢谢

维贾伊