Angular 11使用JitCompilerFactory动态编译组件时依赖注入失效如何解决
使用 JitCompilerFactory 在运行时编译组件可正常运行(参考示例:https://stackoverflow.com/a/67122569/15816951),但向动态编译的组件中注入依赖会失败:
// (1) 定义组件元数据 const metadata = new Component({ selector: "dynamic-selector", template: "This is template: {{text}}" // <---- 插值表达式:可正常运行 }); // (2) 定义组件类并添加装饰器 const compClass = class DynamicComponent { text: string = 'from'; // <---- 插值表达式:可正常运行 constructor(public s: Service1) { } // <---- 尝试注入服务:失败 }; const decoratedComp = Component(metadata)(compClass); // (3) 定义模块类并添加装饰器 const decoratedModule = NgModule({ imports: [...], declarations: [decoratedCmp] })(class DynamicModule { }) // (4) 编译模块并获取组件工厂 const module = compiler.compileModuleAndAllComponentsAsync(decoratedModule); const factory = module.componentFactories.find(x => x.componentType === decoratedCmp); // (5) 使用组件工厂渲染组件 someViewContainerRef.createComponent(factory);
当未在组件类的构造函数中加入依赖注入逻辑(即上述代码第2步)时所有功能正常,但尝试注入依赖会抛出如下错误:
core.js:6210 错误:Promise 中未捕获的错误:该构造函数与Angular依赖注入不兼容,原因是参数列表索引为0处的依赖无效。
出现该问题的原因可能是依赖类型为字符串等原始类型,或该类的父类未添加Angular装饰器。
请检查两点:1)索引为0处的参数类型是否正确;2)该类及其父类是否定义了正确的Angular装饰器。
错误:该构造函数与Angular依赖注入不兼容,原因是参数列表索引为0处的依赖无效。
出现该问题的原因可能是依赖类型为字符串等原始类型,或该类的父类未添加Angular装饰器。
我参考Angular团队针对Angular 9的相关问题解答(https://github.com/angular/angular/issues/35908,该问题针对AoT编译的Ivy项目中使用JIT时出现的DI错误),尝试在 polyfills.ts 中引入 core-js/es/reflect 与 reflect-metadata,但问题仍未解决。请问在Angular 11中,如何实现动态编译组件的依赖注入功能?
解决方案
Angular 11 基于 Ivy 渲染引擎,JIT 模式下的依赖注入依赖构造函数参数的元数据标识,动态生成的类默认不会自动保留这类元数据,是该问题的核心成因,可通过以下三种方式修复:
方案1:显式声明注入元数据
不依赖装饰器自动生成元数据,手动标记构造函数的依赖信息:
import { Inject } from '@angular/core'; // 修改组件类定义 const compClass = class DynamicComponent { text: string = 'from'; // 用Inject装饰器标记服务,同时手动添加参数元数据 constructor(@Inject(Service1) public s: Service1) {} }; // 给类手动挂载参数元数据,供Angular DI识别 (compClass as any).ctorParameters = () => [{ type: Service1 }];如果需要注入可选依赖、值类型依赖,可在
ctorParameters的参数对象中补充对应配置,例如{ type: Service1, decorators: [{ type: Optional }] }。方案2:调整TypeScript编译配置
不想手动写元数据的情况下,修改
tsconfig.json的编译规则,让动态类保留类型元数据:- 确保
compilerOptions中开启"emitDecoratorMetadata": true - 关闭
"useDefineForClassFields": true(Angular 11 默认开启,会破坏动态类的元数据继承) - 不要将
target设置为ES2022及以上版本,过高的ES目标会在编译时删除构造函数类型元数据
- 确保
方案3:使用Ivy原生API定义组件
Ivy引擎优先识别组件类上的
ɵcmp静态字段,可直接显式声明依赖,不需要额外编译模块:import { ɵɵdefineComponent } from '@angular/core'; const compClass = class DynamicComponent { text: string = 'from'; constructor(public s: Service1) {} }; // 手动定义Ivy组件元数据,直接声明依赖 compClass['ɵcmp'] = ɵɵdefineComponent({ type: compClass, selectors: [['dynamic-selector']], template: (rf, ctx) => { if (rf & 1) ctx.text = 'from'; if (rf & 2) ctx.el(0, 'This is template: {{text}}'); }, dependencies: [Service1] });
注意事项
- 不要使用完全匿名的类定义动态组件,尽量给类命名,避免丢失类型元数据
- AoT编译的项目中使用JIT动态编译时,需要在
angular.json的build配置中关闭optimization.removeTypeScriptCompilerMetadata,避免构建时删除元数据 - Angular 11的Ivy模式不依赖
reflect-metadata,无需额外引入相关依赖
内容的提问来源于stack exchange,提问作者Revivius

