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

Rad*_*ler 185 compilation typescript angular2-compiler angular

我想动态创建模板.这应该用于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>
   ...
Run Code Online (Sandbox Code Playgroud)

在另一种情况下这个(string-editor被替换text-editor)

<form>
   <text-editor
     [propertyName]="'code'"
     [entity]="entity"
   ></text-editor>
   ...
Run Code Online (Sandbox Code Playgroud)

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

模板是不断变化的,所以我不能使用ComponentFactoryResolver,并通过现有的...我需要的解决方案与Compiler


AOT和JitCompiler (以前的RuntimeCompiler)

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

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

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

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

Rad*_*ler 156

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

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

这里讨论的类似主题相当于Angular 2中的$ compile.我们需要使用JitCompilerNgModule.NgModule在这里阅读更多关于Angular2的内容:

简而言之

一个工作的plunker/example (动态模板,动态组件类型,动态模块JitCompiler,...在行动中)

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

下面的代码片段(更多的是在这里) -我们的自定义生成器将返回刚刚建立/缓存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;
        //...
    });
Run Code Online (Sandbox Code Playgroud)

这就是它 - 简而言之.要了解更多详情,请阅读以下内容

.

TL&DR

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

.

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

下面对这个场景的描述,我们会

  1. 创建一个模块PartsModule:NgModule (小块的持有者)
  2. 创建另一个模块DynamicModule:NgModule,它将包含我们的动态组件(和PartsModule动态引用)
  3. 创建动态模板(简单方法)
  4. 创建新Component类型(仅当模板已更改时)
  5. 创造新的RuntimeModule:NgModule.该模块将包含先前创建的Component类型
  6. 打电话JitCompiler.compileModuleAndAllComponentsAsync(runtimeModule)ComponentFactory
  7. 创建DynamicComponentView Target占位符和作业的实例ComponentFactory
  8. 分配@Inputs新实例 (从切换INPUTTEXTAREA编辑),消耗@Outputs

NgModule

我们需要一个NgModule.

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

将有一个模块,所有的小部件,例如string-editor,text-editor (date-editor,number-editor...)

@NgModule({
  imports:      [ 
      CommonModule,
      FormsModule
  ],
  declarations: [
      DYNAMIC_DIRECTIVES
  ],
  exports: [
      DYNAMIC_DIRECTIVES,
      CommonModule,
      FormsModule
  ]
})
export class PartsModule { }
Run Code Online (Sandbox Code Playgroud)

哪些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
            ], 
        };
    }
}
Run Code Online (Sandbox Code Playgroud)

检查的使用forRoot()AppModule

最后,我们需要一个特殊的运行时模块..但是稍后将作为工作的一部分创建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
  ],
Run Code Online (Sandbox Code Playgroud)

在那里阅读(阅读)关于NgModule的更多信息:

一个模板生成器

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

entity = { 
    code: "ABC123",
    description: "A description of this Entity" 
};
Run Code Online (Sandbox Code Playgroud)

要创建一个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>";
    }
}
Run Code Online (Sandbox Code Playgroud)

这里的一个技巧是 - 它构建一个使用一些已知属性的模板,例如entity.这样的属性(-ies)必须是动态组件的一部分,我们将在下面创建它.

为了使它更容易一些,我们可以使用接口来定义属性,我们的模板构建器可以使用它.这将由我们的动态组件类型实现.

export interface IHaveDynamicData { 
    public entity: any;
    ...
}
Run Code Online (Sandbox Code Playgroud)

一个ComponentFactory建设者

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

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

因此,我们正在触及我们解决方案的核心.构建器,将1)创建ComponentType2)创建其NgModule3)编译ComponentFactory4)缓存它以供以后重用.

我们需要获得的依赖:

// plunker - app/dynamic/type.builder.ts
import { JitCompiler } from '@angular/compiler';

@Injectable()
export class DynamicTypeBuilder {

  // wee need Dynamic component builder
  constructor(
    protected compiler: JitCompiler
  ) {}
Run Code Online (Sandbox Code Playgroud)

这里有一个片段如何获得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);
            });
    });
}
Run Code Online (Sandbox Code Playgroud)

上面我们创建并缓存两者ComponentModule.因为如果模板(实际上是所有的真实动态部分)是相同的......我们可以重用

这里有两个方法,它们代表了如何在运行时创建装饰类/类型的非常酷的方法.不仅是,@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;
}
Run Code Online (Sandbox Code Playgroud)

重要:

我们的组件动态类型不同,但只是模板.所以我们使用这个事实来缓存它们.这非常重要.Angular2也将缓存由这些.. 类型.如果我们为相同的模板重新创建字符串新类型...我们将开始生成内存泄漏.

ComponentFactory 由托管组件使用

最终作品是一个组件,它承载我们的动态组件的目标,例如<div #dynamicContentPlaceHolder></div>.我们得到它的引用并用于ComponentFactory创建组件.简而言之,这里是该组件的所有部分(如果需要,这里是开放的plunker)

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

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
    ) {}
    ...
Run Code Online (Sandbox Code Playgroud)

我们只接收模板和组件构建器.接下来是我们的示例所需的属性(更多注释)

// 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" 
  };
Run Code Online (Sandbox Code Playgroud)

在这个简单的场景中,我们的托管组件没有任何@Input.所以它不必对变化做出反应.但尽管存在这一事实(并准备好即将发生的变化) - 如果组件已经(首先)已经启动,我们需要引入一些标志.只有这样我们才能开始魔术.

最后,我们将使用我们的组件构建器,它刚刚编译/缓存 ComponentFacotry.我们的目标占位符将被要求实例化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;
        //...
    });
}
Run Code Online (Sandbox Code Playgroud)

小扩展

此外,我们需要保持对已编译模板的引用..以便能够正确地进行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;
  }
}
Run Code Online (Sandbox Code Playgroud)

DONE

这就是它.不要忘记销毁任何动态构建的东西(ngOnDestroy).此外,请确保缓存动态types,modules如果唯一的区别是他们的模板.

这里检查一切

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

  • 这看起来像是一个如此复杂的解决方案,不推荐的解决方案非常简单明了,还有其他方法吗? (47认同)
  • @RadimKöhler - 我试过这个例子.它没有AOT工作.但是,当我尝试使用AOT运行时,它显示错误"找不到RuntimeComponentModule的NgModule元数据".你能帮我解决这个错误吗? (7认同)
  • @ribsies感谢您的留言.让我澄清一下.许多其他答案试图让它变得简单***.但我试图解释它,并在一个场景中显示它,关闭**实际使用**.我们需要缓存内容,我们必须在重新创建时调用destroy等等.所以,虽然动态构建的魔力确实在你指出的`type.builder.ts`中,但我希望,任何用户都会了解如何将所有内容置于上下文中...希望它可能有用;) (5认同)
  • 答案本身就是完美的!但对于现实生活中的应用并不切实可行.角度团队应该在框架中为此提供解决方案,因为这是业务应用程序中的常见要求.如果没有,则必须询问Angular 2是否适合业务应用程序. (4认同)
  • 我认为和@tibbus的方式相同:这比以前弃用的代码要复杂得多.谢谢你的回答. (3认同)

Ren*_*ger 54

编辑(2017年8月26日):下面的解决方案适用于Angular2和4.我已将其更新为包含模板变量并单击处理程序并使用Angular 4.3进行测试.
对于Angular4,Ophir的答案中描述的ngComponentOutlet 是一个更好的解决方案.但是现在它还不支持输入和输出.如果[this PR](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 {}
Run Code Online (Sandbox Code Playgroud)

住在http://plnkr.co/edit/fdP9Oc.

  • 我会说,这是一个例子,如何尽可能少地编写*代码*与我的答案http://stackoverflow.com/a/38888009/1679310**相同.在这种情况下,它应该是有用的情况*(主要是RE生成模板)*当条件改变时...带有`const模板`的简单`ngAfterViewInit`调用将不起作用.但是如果你的任务是减少上面详细描述的方法*(创建模板,创建组件,创建模块,编译它,创建工厂..创建实例)*...你可能做到了 (3认同)

小智 49

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

我最终做的是使用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;
  }
}
Run Code Online (Sandbox Code Playgroud)
  • 简短说明:
    1. my-component - 动态组件呈现的组件
    2. DynamicComponent - 要动态构建的组件,它在my-component中呈现

不要忘记将所有角度库升级到^ Angular 4.0.0

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

UPDATE

也适用于角5.

  • 以下是从Angular快速入门开始的简单示例:https://embed.plnkr.co/9L72KpobVvY14uiQjo4p/ (7认同)
  • 此解决方案是否适用于"ng build --prod"?似乎编译器类和AoT不适合atm. (5认同)
  • 这对我来说非常适合Angular4.我必须做的唯一调整是能够为动态创建的RuntimeComponentModule指定导入模块. (3认同)
  • @OphirStern我还发现这种方法在Angular 5中运行良好,但没有使用--prod构建标志. (2认同)
  • 我用角度5(5.2.8)使用JitCompilerFactory测试它并使用--prod标志不起作用!有没有人有办法解决吗?(没有--prod标志的BTW JitCompilerFactory完美无缺) (2认同)

Ste*_*aul 16

我决定将我学到的所有东西压缩成一个文件.与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 {}`
Run Code Online (Sandbox Code Playgroud)


Ste*_*aul 13

2019年6月答案

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

在撰写本文时,我没有发现上述官方文档特别有用(特别是在向动态组件发送数据和从动态组件接收事件方面)。总之,您将需要:

步骤1)更新您的 AppModule

PortalModulecdk包中导入并在其中注册动态组件entryComponents

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

步骤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{
}
Run Code Online (Sandbox Code Playgroud)

实际观看

步骤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 = '';
}
Run Code Online (Sandbox Code Playgroud)

实际观看

  • @StephenPaul 这种`Portal` 方法与`ngTemplateOutlet` 和`ngComponentOutlet` 有何不同? (4认同)
  • @Gi1ber7 我知道对吗?为什么他们花了这么长时间? (2认同)
  • 我同意这解决了如何使用门户创建动态组件,但我不太清楚这如何允许 OP 使用这些动态组件创建动态模板。似乎 MyDynamicComponent 中的模板是由 AOT 编译的,其中的动态部分只是组件/门户部分。所以这似乎是答案的一半,但不是全部答案。 (2认同)

Lon*_*eld 9

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

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

@Component({template: template1})
class DynamicComponent {}
Run Code Online (Sandbox Code Playgroud)

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

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

@Component({
  template: template1,

})
class DynamicComponent {

}
@NgModule({
  imports: [BrowserModule,RouterModule],
  declarations: [DynamicComponent]
})
class DynamicModule { }
Run Code Online (Sandbox Code Playgroud)

(这里导入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

  • 所以,你已经声明了module1,module2,module3.如果你需要另一个"动态"模板内容,你需要创建一个defoudtion(文件)表单moudle4(module4.ts),对吗?如果是,那似乎不是动态的.它是静态的,不是吗?或者我会错过什么? (3认同)

Mos*_*sta 6

到 2021 年,Angular 仍然无法使用动态 HTML 创建组件(动态加载 html 模板),只是为了节省您的时间。

即使有很多已投票通过的解决方案和已接受的解决方案,但至少目前它们都不适用于生产/AOT 中的最新版本。

基本上是因为 Angular 不允许您使用以下方式定义组件: template: {variable}

正如 Angular 团队所说,他们不会支持这种方法!请找到此参考https://github.com/angular/angular/issues/15275


Seb*_*ian 5

跟进 Radmin 的出色回答,使用 angular-cli 1.0.0-beta.22 及更高版本的每个人都需要进行一些调整。

COMPILER_PROVIDERS无法再导入(有关详细信息,请参阅angular-cli GitHub)。

因此,解决方法是根本不使用COMPILER_PROVIDERSandJitCompiler在该providers部分中,而是JitCompilerFactory在类型构建器类中使用from '@angular/compiler' 而不是这样:

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

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

  • 有人有使用 AOT 示例的 JitCompiletFactory 吗?我和@Cybey 有同样的错误 (2认同)

Ric*_*ltz 5

只需使用ng-dynamic中dynamicComponent指令,就可以在Angular 2 Final版本中解决这个问题.

用法:

<div *dynamicComponent="template; context: {text: text};"></div>
Run Code Online (Sandbox Code Playgroud)

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


Ole*_*Pnk 5

在角度7.x中,我为此使用了角度元素。

  1. 在@ angular / elements -s中安装@ angular-elements npm

  2. 创建附件服务。

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

Run Code Online (Sandbox Code Playgroud)

请注意,自定义元素标签必须与角度分量选择器不同。在AppUserIconComponent中:

...
selector: app-user-icon
...
Run Code Online (Sandbox Code Playgroud)

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

  1. 然后,您必须在AppComponent中调用register:
@Component({
  selector: 'app-root',
  template: '<router-outlet></router-outlet>'
})
export class AppComponent {
  constructor(   
    dynamicComponents: DynamicComponentsService,
  ) {
    dynamicComponents.register();
  }

}
Run Code Online (Sandbox Code Playgroud)
  1. 现在,您可以在代码的任何位置使用它,如下所示:
dynamicComponents.create('user-icon', {user:{...}});
Run Code Online (Sandbox Code Playgroud)

或像这样:

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

this.content = this.domSanitizer.bypassSecurityTrustHtml(html);
Run Code Online (Sandbox Code Playgroud)

(在模板中):

<div class="comment-item d-flex" [innerHTML]="content"></div>
Run Code Online (Sandbox Code Playgroud)

请注意,在第二种情况下,您必须传递带有JSON.stringify的对象,然后再对其进行解析。我找不到更好的解决方案。


归档时间:

查看次数:

128632 次

最近记录:

6 年,5 月 前