首页 文章

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

提问于
浏览
150

我想 dynamically 创建 template . 这应该用于在 Runtime 构建 ComponentType 并将其放置(甚至替换)它在托管组件内的某个位置 .

直到RC4我使用 ComponentResolver ,但是使用RC5我收到消息:

不建议使用ComponentResolver进行动态编译 . 请将ComponentFactoryResolver与@ NgModule / @ Component.entryComponents或ANALYZE_FOR_ENTRY_COMPONENTS提供程序一起使用 . 对于仅运行时编译,您还可以使用Compiler.compileComponentSync / Async .

我发现了这个(官方的angular2)文件

Angular 2同步动态组件创建

并了解我可以使用其中之一

  • 种类 ngIfComponentFactoryResolver . 如果我将已知组件传递到 @Component({entryComponents: [comp1, comp2], ...}) 内部托管一个 - 我可以使用 .resolveComponentFactory(componentToRender);

  • 实时运行时编译, Compiler ...

但问题是如何使用 Compiler ?上面的注释说我应该打电话: 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>
   ...

依此类推(不同的数字/日期/参考 editors 属性类型,跳过一些用户的某些属性......) . 即这是一个例子,真正的配置可以生成更多不同和复杂的模板 .

template is changing ,所以我不能使用 ComponentFactoryResolver 并传递现有的...我需要 Compiler 的解决方案


AOT和JitCompiler(以前的RuntimeCompiler)

您是否希望将此功能与AOT一起使用(提前编译)?你得到了:

错误:静态解析符号值时出错 . 不支持函数调用 . 考虑使用对导出函数的引用替换函数或lambda(原始.ts文件中的位置65:17),在... / node_modules/@angular/compiler/src/compiler.d.ts中解析符号COMPILER_PROVIDERS,

请留下您的评论,在这里投票:

AOT会支持使用COMPILER_PROVIDERS进行编码吗?

11 回答

  • 140

    编辑 - 与2.3.0(2016-12-07)相关

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

    类似的主题在这里讨论Equivalent of $compile in Angular 2 . 我们需要使用 JitCompilerNgModule . 在Angular2中阅读有关 NgModule 的更多信息:

    在果壳中

    a working plunker/example (动态模板,动态组件类型,动态模块, JitCompiler ,......正在运行)

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

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

    观察一个plunker并回来阅读详细信息,以防一些代码片段需要更多解释

    .

    详细说明 - Angular2 RC6和运行时组件

    下面是this scenario的描述,我们会的

    • 创建一个模块 PartsModule:NgModule (小块的持有者)

    • 创建另一个模块 DynamicModule:NgModule ,它将包含我们的动态组件(并动态引用 PartsModule

    • 创建动态模板(简单方法)

    • 创建新的 Component 类型(仅当模板已更改时)

    • 创建新的 RuntimeModule:NgModule . 该模块将包含先前创建的 Component 类型

    • 致电 JitCompiler.compileModuleAndAllComponentsAsync(runtimeModule) 获取 ComponentFactory

    • 创建 DynamicComponent 的实例 - View Target占位符的作业和 ComponentFactory

    • @Inputs 分配给 new instance (从 INPUT 切换到 TEXTAREA 编辑),消耗 @Outputs

    NgModule

    我们需要一个 NgModule .

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

    所有小部件都有 one 模块,例如: string-editortext-editordate-editornumber-editor ...)

    @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
                ], 
            };
        }
    }
    

    检查AppModule中forRoot()的用法

    最后,我们需要一个特殊的运行时模块..但是稍后将作为 DynamicTypeBuilder job的一部分创建 .

    第四个模块,应用程序模块,是保持声明编译器提供程序的人:

    ...
    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的更多信息:

    模板构建器

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

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

    要创建一个 template ,在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;
        ...
    }
    

    ComponentFactory构建器

    这里非常重要的是要记住:

    我们的组件类型,使用我们的DynamicTypeBuilder构建,可能会有所不同 - 但只能通过其模板(在上面创建) . 组件的属性(输入,输出或某些受保护的)仍然相同 . 如果我们需要不同的属性,我们应该定义Template和Type Builder的不同组合

    因此,我们正在触及我们解决方案的核心 . 构建器,将1)创建 ComponentType 2)创建它 NgModule 3)编译 ComponentFactory 4) cache 它以供以后重用 .

    我们需要获得的依赖:

    // 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 来创建一个组件 . 简而言之,这里是该组件的所有部分(如果需要,打开plunker here

    我们首先总结一下导入语句:

    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 . 所以它不必对变化做出反应 . 但尽管存在这一事实(并准备好即将发生的变化) - 如果组件已经(首先)已经启动,我们需要引入一些标志 . 只有这样我们才能开始魔术 .

    最后,我们将使用我们的组件构建器,它刚刚编译/缓存 ComponentFacotry . 我们的目标占位符将被要求用该工厂实例化 the Component .

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

    小扩展

    此外,我们需要保持对已编译模板的引用..以便能够正确 destroy() 它,每当我们将更改它 .

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

    完成了

    这就是它 . 不要忘记 Destroy 动态构建的任何内容(ngOnDestroy) . 另外,如果唯一的区别是他们的模板,请务必 cache dynamic typesmodules .

    检查一切here

    查看此帖子的先前版本(例如RC5相关),查看历史记录

  • 4

    EDIT (26/08/2017) :下面的解决方案适用于Angular2和4.我已将其更新为包含模板变量并单击处理程序并使用Angular 4.3进行测试 .
    对于Angular4,Ophir's answer中描述的ngComponentOutlet是一个更好的解决方案 . 但是现在呢does not support inputs & outputs . 如果[此PR]([https://github.com/angular/angular/pull/15362]](https://github.com/angular/angular/pull/15362])被接受,则可以通过该组件create事件返回的实例 .
    ng-dynamic-component可能是最好和最简单的解决方案,但我还没有测试过 .

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

  • 14

    我一定是迟到了,这里没有一个解决方案似乎对我有帮助 - 太乱了,觉得太多了解决方法 .

    我最终做的是使用 Angular 4.0.0-beta.6ngComponentOutlet .

    这给了我所有写在动态组件文件中的最简单,最简单的解决方案 .

    • 这是一个简单的示例,它只接收文本并将其放在模板中,但显然您可以根据需要进行更改:
    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;
      }
    }
    
    • Short explanation:

    • my-component - 动态组件呈现的组件

    • DynamicComponent - 要动态构建的组件,它在my-component中呈现

    Don't forget to upgrade all the angular libraries to ^Angular 4.0.0

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

    UPDATE

    也适用于角5 .

  • 4

    I decided to compact everything I learned into one file . 与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 {}`
    
  • 8

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

    比如说,你有一个动态的html模板= template1并想要动态加载,首先包装到组件中

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

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

    从rc6开始,必须有@NgModule包装这个组件 . @NgModule,就像anglarJS 1中的模块一样,它解耦了ng2应用程序的不同部分,因此:

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

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

    现在您可以将DynamicModule编译为: this.compiler.compileModuleAndAllComponentsAsync(DynamicModule).then( factory => factory.componentFactories.find(x => x.componentType === DynamicComponent))

    我们需要在app.moudule.ts上面加载它,请看我的app.moudle.ts . 有关更多详细信息,请查看:https://github.com/Longfld/DynamicalRouter/blob/master/app/MyRouterLink.ts和app.moudle.ts

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

  • 43

    我想在Radim的这篇非常优秀的帖子之上添加一些细节 .

    我采用了这个解决方案,并对它进行了一些研究并迅速遇到了一些限制 . 我将概述那些,然后给出解决方案 .

    • 首先,我无法在动态细节内部渲染动态细节(基本上是彼此嵌套的动态UI) .

    • 下一个问题是我想在解决方案中提供的部分内部呈现动态细节 . 初始解决方案也是不可能的 .

    • 最后,无法在字符串编辑器等动态部件上使用模板URL .

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

    recursive dynamic template compilation in angular2

    如果您遇到与我相同的问题,我将概述这些限制的答案,因为这使得解决方案更加灵活 . 让初始的plunker更新也很棒 .

    要在彼此内部嵌套动态详细信息,您需要在 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.tsdynamic.module.ts

    里面 parts.module.ts 你需要在 DYNAMIC_DIRECTIVES 中添加 DynamicDetail

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

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

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

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

    最后,无法在动态组件上创建的部件中使用模板 . 解决方案(或解决方法 . 我不确定它是否是一个角度错误或框架的错误使用)是在构造函数中创建编译器而不是注入它 .

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

    希望这有助于其他人!

    最好的问候莫滕

  • 2

    关注Radmin的优秀答案,对于使用angular-cli版本1.0.0-beta.22及更高版本的每个人都需要进行一些调整 .

    COMPILER_PROVIDERS can no longer be imported (有关详细信息,请参阅angular-cli GitHub) .

    所以解决方法是在 providers 部分根本不使用 COMPILER_PROVIDERSJitCompiler ,但是在类型构建器类中使用 JitCompilerFactory 来代替'@angular/compiler':

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

    如您所见,它不是可注射的,因此与DI无依赖关系 . 此解决方案也适用于不使用的项目角-CLI .

  • 49

    只需使用ng-dynamic中的dynamicComponent指令解决了Angular 2 Final版本中的问题 .

    用法:

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

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

  • 1

    我自己试图看看如何将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]表达式中的“[”有意义,这表示单向数据绑定,因此如果您知道不需要将属性绑定到变量,则可以甚至省略它们 .

  • -1

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

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

    此示例是动态表单控件是添加组件(这是您可以从服务器获取Formcontrols的地方) . 如果您看到addcomponent方法,则可以看到Forms控件 . 在这个例子中,我没有使用角度材料,但它有效(我正在使用@work) . 这是目标角度6,但适用于所有以前的版本 .

    需要为AngularVersion 5及更高版本添加JITComplierFactory .

    谢谢

    维杰

  • 4

    在Ophir Stern的答案之上,这是一个与Angular 4中的AoT一起使用的变体 . 我唯一的问题是我无法将任何服务注入DynamicComponent,但我可以忍受 .

    注意:我没有使用Angular 5进行测试 .

    import { Component, OnInit, Input, NgModule, NgModuleFactory, Compiler, EventEmitter, Output } from '@angular/core';
    import { JitCompilerFactory } from '@angular/compiler';
    
    export function createJitCompiler() {
      return new JitCompilerFactory([{
        useDebug: false,
        useJit: true
      }]).createCompiler();
    }
    
    type Bindings = {
      [key: string]: any;
    };
    
    @Component({
      selector: 'app-compile',
      template: `
        <div *ngIf="dynamicComponent && dynamicModule">
          <ng-container *ngComponentOutlet="dynamicComponent; ngModuleFactory: dynamicModule;">
          </ng-container>
        </div>
      `,
      styleUrls: ['./compile.component.scss'],
      providers: [{provide: Compiler, useFactory: createJitCompiler}]
    })
    export class CompileComponent implements OnInit {
    
      public dynamicComponent: any;
      public dynamicModule: NgModuleFactory<any>;
    
      @Input()
      public bindings: Bindings = {};
      @Input()
      public template: string = '';
    
      constructor(private compiler: Compiler) { }
    
      public ngOnInit() {
    
        try {
          this.loadDynamicContent();
        } catch (err) {
          console.log('Error during template parsing: ', err);
        }
    
      }
    
      private loadDynamicContent(): void {
    
        this.dynamicComponent = this.createNewComponent(this.template, this.bindings);
        this.dynamicModule = this.compiler.compileModuleSync(this.createComponentModule(this.dynamicComponent));
    
      }
    
      private createComponentModule(componentType: any): any {
    
        const runtimeComponentModule = NgModule({
          imports: [],
          declarations: [
            componentType
          ],
          entryComponents: [componentType]
        })(class RuntimeComponentModule { });
    
        return runtimeComponentModule;
    
      }
    
      private createNewComponent(template: string, bindings: Bindings): any {
    
        const dynamicComponent = Component({
          selector: 'app-dynamic-component',
          template: template
        })(class DynamicComponent implements OnInit {
    
          public bindings: Bindings;
    
          constructor() { }
    
          public ngOnInit() {
            this.bindings = bindings;
          }
    
        });
    
        return dynamicComponent;
    
      }
    
    }
    

    希望这可以帮助 .

    干杯!

相关问题