我想动态创建一个模板。这应该用于在运行时构建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和传递现有的…我需要一个解决方案与编译器。


当前回答

如果你只需要一种解析动态字符串和通过选择器加载组件的方法,你可能还会发现ngx-dynamic-hooks库很有用。我最初创建这个是作为个人项目的一部分,但在周围没有看到类似的东西,所以我对它进行了一些优化并将其公之于众。

一些tidbids:

You can load any components into a dynamic string by their selector (or any other pattern of your choice!) Inputs and outputs can be se just like in a normal template Components can be nested without restrictions You can pass live data from the parent component into the dynamically loaded components (and even use it to bind inputs/outputs) You can control which components can load in each outlet and even which inputs/outputs you can give them The library uses Angular's built-in DOMSanitizer to be safe to use even with potentially unsafe input.

值得注意的是,它不像这里的其他响应那样依赖于运行时编译器。因此,您不能使用模板语法。另一方面,这意味着它既可以在JiT和aot模式下工作,也可以在Ivy和旧的模板引擎下工作,而且在一般情况下使用起来更加安全。

在这部《斯塔克布利茨》中可以看到它的作用。

其他回答

我想在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 2 Final版本中,通过使用ng-dynamic中的dynamicComponent指令解决了这个问题。

用法:

<div *dynamicComponent="template; context: {text: text};"></div>

其中template是您的动态模板,context可以设置为您希望模板绑定到的任何动态数据模型。

我决定把我学到的东西都压缩到一个文件里。 与RC5之前相比,这里有很多东西值得考虑。注意,这个源文件包括AppModule和AppComponent。

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

@Component({
  selector: 'app-dynamic',
  template: '<h4>Dynamic Components</h4><br>'
})
export class DynamicComponentRenderer implements OnInit {

  factory: ModuleWithComponentFactories<DynamicModule>;

  constructor(private vcRef: ViewContainerRef, private compiler: Compiler) { }

  ngOnInit() {
    if (!this.factory) {
      const dynamicComponents = {
        sayName1: {comp: SayNameComponent, inputs: {name: 'Andrew Wiles'}},
        sayAge1: {comp: SayAgeComponent, inputs: {age: 30}},
        sayName2: {comp: SayNameComponent, inputs: {name: 'Richard Taylor'}},
        sayAge2: {comp: SayAgeComponent, inputs: {age: 25}}};
      this.compiler.compileModuleAndAllComponentsAsync(DynamicModule)
        .then((moduleWithComponentFactories: ModuleWithComponentFactories<DynamicModule>) => {
          this.factory = moduleWithComponentFactories;
          Object.keys(dynamicComponents).forEach(k => {
            this.add(dynamicComponents[k]);
          })
        });
    }
  }

  addNewName(value: string) {
    this.add({comp: SayNameComponent, inputs: {name: value}})
  }

  addNewAge(value: number) {
    this.add({comp: SayAgeComponent, inputs: {age: value}})
  }

  add(comp: any) {
    const compFactory = this.factory.componentFactories.find(x => x.componentType === comp.comp);
    // If we don't want to hold a reference to the component type, we can also say: const compFactory = this.factory.componentFactories.find(x => x.selector === 'my-component-selector');
    const injector = ReflectiveInjector.fromResolvedProviders([], this.vcRef.parentInjector);
    const cmpRef = this.vcRef.createComponent(compFactory, this.vcRef.length, injector, []);
    Object.keys(comp.inputs).forEach(i => cmpRef.instance[i] = comp.inputs[i]);
  }
}

@Component({
  selector: 'app-age',
  template: '<div>My age is {{age}}!</div>'
})
class SayAgeComponent {
  @Input() public age: number;
};

@Component({
  selector: 'app-name',
  template: '<div>My name is {{name}}!</div>'
})
class SayNameComponent {
  @Input() public name: string;
};

@NgModule({
  imports: [BrowserModule],
  declarations: [SayAgeComponent, SayNameComponent]
})
class DynamicModule {}

@Component({
  selector: 'app-root',
  template: `
        <h3>{{message}}</h3>
        <app-dynamic #ad></app-dynamic>
        <br>
        <input #name type="text" placeholder="name">
        <button (click)="ad.addNewName(name.value)">Add Name</button>
        <br>
        <input #age type="number" placeholder="age">
        <button (click)="ad.addNewAge(age.value)">Add Age</button>
    `,
})
export class AppComponent {
  message = 'this is app component';
  @ViewChild(DynamicComponentRenderer) dcr;

}

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

在Radmin的精彩回答之后,每个使用angular-cli 1.0.0-beta版本的人都需要做一个小小的调整。22岁及以上。

COMPILER_PROVIDERScan不再被导入(详见angular-cli GitHub)。

因此,这里的解决方法是在providers部分完全不使用COMPILER_PROVIDERS和JitCompiler,而是在类型构建器类中使用'@angular/compiler'中的JitCompilerFactory,就像这样:

private compiler: Compiler = new JitCompilerFactory([{useDebug: false, useJit: true}]).createCompiler();

正如您所看到的,它是不可注入的,因此与DI没有依赖关系。这个解决方案也适用于不使用angular-cli的项目。

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

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

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

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

谢谢

维贾伊