【问题标题】:What is the recommended way to dynamically compile component in Angular 11?在 Angular 11 中动态编译组件的推荐方法是什么?
【发布时间】:2021-07-10 11:48:22
【问题描述】:

在我们的应用程序中,我们使用以下指令来显示动态 Angular 组件:

import {Compiler, Component, Directive, Input, ModuleWithComponentFactories, NgModule, OnDestroy, ViewContainerRef} from '@angular/core';
import {Render} from './render';

@Directive({
  selector: '[appRender]',
  exportAs: 'appRender'
})
export class RenderDirective implements OnDestroy {

  @Input()
  public set model(model: Render) {
    this.compile(model);
  }

  constructor(private readonly viewContainerRef: ViewContainerRef,
              private readonly compiler: Compiler) {
  }

  private compile(model: Render) {
    @Component({template: model.template})
    class TemplateComponent {
    }

    @NgModule({
      imports: model.imports,
      declarations: [TemplateComponent]
    })
    class TemplateModule {
    }

    this.compiler.compileModuleAndAllComponentsAsync(TemplateModule).then((factories: ModuleWithComponentFactories<any>) => {
      const factory = factories.componentFactories.find(component => component.componentType === TemplateComponent);
      const componentRef = this.viewContainerRef.createComponent(factory);
      Object.assign(componentRef.instance, model.instance);
      componentRef.hostView.detectChanges();
    });
  }

  ngOnDestroy(): void {
    this.viewContainerRef.clear();
  }
}

我们正处于从 8.2 到 11 的 Angular 更新中间。更新后我们面临以下错误:

ERROR Error: Angular JIT compilation failed: '@angular/compiler' not loaded!
  - JIT compilation is discouraged for production use-cases! Consider AOT mode instead.
  - Did you bootstrap using '@angular/platform-browser-dynamic' or '@angular/platform-server'?
  - Alternatively provide the compiler with 'import "@angular/compiler";' before bootstrapping.
    at getCompilerFacade (core.js:4086)
    at Function.get (core.js:26924)
    at getNgModuleDef (core.js:1139)
    at new NgModuleFactory$1 (core.js:25317)
    at Compiler_compileModuleSync__POST_R3__ (core.js:28165)
    at Compiler_compileModuleAndAllComponentsSync__POST_R3__ (core.js:28175)
    at Compiler_compileModuleAndAllComponentsAsync__POST_R3__ [as compileModuleAndAllComponentsAsync] (core.js:28188)
    at RenderDirective.compile (common.js:4560)
    at RenderDirective.set model [as model] (common.js:4541)
    at setInputsForProperty (core.js:10961)

我相信这与 IVY 编译器有关。问题是在 Angular 11 中实现相同结果的推荐方法是什么?

【问题讨论】:

  • 我认为 Ivy 默认使用 AOT。并且当使用 AOT 时,编译器没有默认提供程序(因为无论如何它都是自重的)。如果要在运行时编译模板,则必须禁用 AOT 或在要使用编译器的模块的提供程序中显式提供编译器。

标签: angular ivy


【解决方案1】:

我设法通过以下方式解决了这个问题:

  1. 在 RenderModule 中提供 JitCompilerFactory
@NgModule({
  declarations: [RenderDirective],
  exports: [RenderDirective],
  providers: [{provide: COMPILER_OPTIONS, useValue: {useJit: true}, multi: true},
    {provide: CompilerFactory, useClass: JitCompilerFactory, deps: [COMPILER_OPTIONS]},
    {provide: Compiler, useFactory: createCompiler, deps: [CompilerFactory]}]
})
export class RenderModule {
}
  1. 用函数定义替换带注释的组件和模块:
// BEFORE
  private compile(model: Render) {
    @Component({template: model.template})
    class TemplateComponent {
    }

    @NgModule({
      imports: model.imports,
      declarations: [TemplateComponent]
    })
    class TemplateModule {
    }

    this.compiler.compileModuleAndAllComponentsAsync(TemplateModule).then((factories: ModuleWithComponentFactories<any>) => {
      const factory = factories.componentFactories.find(component => component.componentType === TemplateComponent);
      const componentRef = this.viewContainerRef.createComponent(factory);
      Object.assign(componentRef.instance, model.instance);
      componentRef.hostView.detectChanges();
    });
  }
// AFTER
  private compile(model: Render) {
    const templateComponent = Component({template: model.template})(class {});
    const templateModule = NgModule({
        imports: model.imports,
        declarations: [templateComponent]
    })(class {});

    this.compiler.compileModuleAndAllComponentsAsync(templateModule).then((factories: ModuleWithComponentFactories<any>) => {
      const factory = factories.componentFactories.find(component => component.componentType === templateComponent);
      const componentRef = this.viewContainerRef.createComponent(factory);
      Object.assign(componentRef.instance, model.instance);
      componentRef.hostView.detectChanges();
    }).catch(err => console.error(err));
  }

【讨论】:

    【解决方案2】:

    这可行,但不能注入到 TemplateComponent。

     @Component({template: model.template})
     class TemplateComponent {
    
         constructor(private service: SomeService) { }
    
     }
    

    core.js:6210 ERROR 错误:未捕获(在承诺中):错误:此构造函数与 Angular 依赖注入不兼容,因为它在参数列表索引 0 处的依赖无效。 如果依赖类型是像字符串这样的原始类型,或者此类的祖先缺少 Angular 装饰器,则可能会发生这种情况。

    在这里详细查看我的问题:Angular 11 DI for dynamically compiled components

    【讨论】:

      猜你喜欢
      • 2020-01-30
      • 1970-01-01
      • 2018-02-17
      • 2012-02-05
      • 2021-07-25
      • 2014-02-21
      • 1970-01-01
      • 2019-12-23
      • 1970-01-01
      相关资源
      最近更新 更多