我想动态创建一个模板。这应该用于在运行时构建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 Final版本中,通过使用ng-dynamic中的dynamicComponent指令解决了这个问题。

用法:

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

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

其他回答

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

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

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

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

谢谢

维贾伊

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

我最终使用了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。

我自己也在尝试如何将RC4更新到RC5,因此我偶然发现了这个条目和动态组件创建的新方法,对我来说仍然有点神秘,所以我不会建议任何关于组件工厂解析器的建议。

但是,我能建议的是在这种情况下创建组件的更清晰的方法-只需在模板中使用switch,根据某些条件创建字符串编辑器或文本编辑器,就像这样:

<form [ngSwitch]="useTextarea">
    <string-editor *ngSwitchCase="false" propertyName="'code'" 
                 [entity]="entity"></string-editor>
    <text-editor *ngSwitchCase="true" propertyName="'code'" 
                 [entity]="entity"></text-editor>
</form>

顺便说一下,[prop]表达式中的“[”有一个含义,这表明了一种数据绑定方式,因此,如果你知道你不需要将属性绑定到变量,你可以甚至应该省略这些。

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。

在角7。我使用了角元素。

安装@angular-elements NPM I @angular/elements 创建配件服务。

import { Injectable, Injector } from '@angular/core';
import { createCustomElement } from '@angular/elements';
import { IStringAnyMap } from 'src/app/core/models';
import { AppUserIconComponent } from 'src/app/shared';

const COMPONENTS = {
  'user-icon': AppUserIconComponent
};

@Injectable({
  providedIn: 'root'
})
export class DynamicComponentsService {
  constructor(private injector: Injector) {

  }

  public register(): void {
    Object.entries(COMPONENTS).forEach(([key, component]: [string, any]) => {
      const CustomElement = createCustomElement(component, { injector: this.injector });
      customElements.define(key, CustomElement);
    });
  }

  public create(tagName: string, data: IStringAnyMap = {}): HTMLElement {
    const customEl = document.createElement(tagName);

    Object.entries(data).forEach(([key, value]: [string, any]) => {
      customEl[key] = value;
    });

    return customEl;
  }
}

注意,你的自定义元素标签必须与angular组件选择器不同。 在AppUserIconComponent:

...
selector: app-user-icon
...

在这种情况下,自定义标签名称我使用“user-icon”。

然后你必须在AppComponent中调用register:

@Component({
  selector: 'app-root',
  template: '<router-outlet></router-outlet>'
})
export class AppComponent {
  constructor(   
    dynamicComponents: DynamicComponentsService,
  ) {
    dynamicComponents.register();
  }

}

现在在你代码的任何地方你都可以这样使用它:

dynamicComponents.create('user-icon', {user:{...}});

或者像这样:

const html = `<div class="wrapper"><user-icon class="user-icon" user='${JSON.stringify(rec.user)}'></user-icon></div>`;

this.content = this.domSanitizer.bypassSecurityTrustHtml(html);

(模板):

<div class="comment-item d-flex" [innerHTML]="content"></div>

注意,在第二种情况下,必须传递带有JSON的对象。Stringify,然后再次解析它。我找不到更好的解决办法了。