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


当前回答

2019年6月答案

好消息!看起来@angular/cdk包现在对门户提供了一流的支持!

在撰写本文时,我并没有发现上面的官方文档有什么特别的帮助(特别是在向动态组件发送数据和从动态组件接收事件方面)。总之,你需要:

步骤1)更新AppModule

从@angular/cdk/portal包中导入PortalModule,在entryComponents中注册你的动态组件

@NgModule({
  declarations: [ ..., AppComponent, MyDynamicComponent, ... ]
  imports:      [ ..., PortalModule, ... ],
  entryComponents: [ ..., MyDynamicComponent, ... ]
})
export class AppModule { }

步骤2。选项A:如果你不需要向动态组件传递数据和从动态组件接收事件:

@Component({
  selector: 'my-app',
  template: `
    <button (click)="onClickAddChild()">Click to add child component</button>
    <ng-template [cdkPortalOutlet]="myPortal"></ng-template>
  `
})
export class AppComponent  {
  myPortal: ComponentPortal<any>;
  onClickAddChild() {
    this.myPortal = new ComponentPortal(MyDynamicComponent);
  }
}

@Component({
  selector: 'app-child',
  template: `<p>I am a child.</p>`
})
export class MyDynamicComponent{
}

看看它的实际应用

步骤2。选项B:如果你确实需要向动态组件传递数据并从动态组件接收事件:

// A bit of boilerplate here. Recommend putting this function in a utils 
// file in order to keep your component code a little cleaner.
function createDomPortalHost(elRef: ElementRef, injector: Injector) {
  return new DomPortalHost(
    elRef.nativeElement,
    injector.get(ComponentFactoryResolver),
    injector.get(ApplicationRef),
    injector
  );
}

@Component({
  selector: 'my-app',
  template: `
    <button (click)="onClickAddChild()">Click to add random child component</button>
    <div #portalHost></div>
  `
})
export class AppComponent {

  portalHost: DomPortalHost;
  @ViewChild('portalHost') elRef: ElementRef;

  constructor(readonly injector: Injector) {
  }

  ngOnInit() {
    this.portalHost = createDomPortalHost(this.elRef, this.injector);
  }

  onClickAddChild() {
    const myPortal = new ComponentPortal(MyDynamicComponent);
    const componentRef = this.portalHost.attach(myPortal);
    setTimeout(() => componentRef.instance.myInput 
      = '> This is data passed from AppComponent <', 1000);
    // ... if we had an output called 'myOutput' in a child component, 
    // this is how we would receive events...
    // this.componentRef.instance.myOutput.subscribe(() => ...);
  }
}

@Component({
  selector: 'app-child',
  template: `<p>I am a child. <strong>{{myInput}}</strong></p>`
})
export class MyDynamicComponent {
  @Input() myInput = '';
}

看看它的实际应用

其他回答

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

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

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

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

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

谢谢

维贾伊

我自己也在尝试如何将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]表达式中的“[”有一个含义,这表明了一种数据绑定方式,因此,如果你知道你不需要将属性绑定到变量,你可以甚至应该省略这些。