首页
/ Angular NG0204 错误(Invalid Injection Token)完全指南:成因、诊断与修复

Angular NG0204 错误(Invalid Injection Token)完全指南:成因、诊断与修复

2026-09-07 19:29:44作者:申梦珏Efrain

NG0204(Invalid Injection Token)是 Angular 依赖注入(Dependency Injection, DI)系统中标识"无法为某 Token 解析出可用的工厂/提供者"的运行时错误,在实际开发中常以"启动即崩溃"的方式出现,排查体验差。本文以本仓库 @angular/core 源码与其官方错误说明为准,先讲清该错误在 DI 解析链路中的触发位置与两条典型报错文案的含义,再逐条拆解最常出现的三种触发场景(缺少服务装饰器、InjectionToken 未配置 Provider、构造器参数不可解析),最后给出可落地的排查清单与修复代码。读完本文,你将能够仅凭报错信息与调用栈,快速定位并修复此类注入失败问题。

NG0204 是什么:一个错误的两个触发点

packages/core/src/errors.ts 中,NG0204 对应 RuntimeErrorCode.INVALID_INJECTION_TOKEN = -204,属于 core 包错误区间(100–999)中的"依赖注入类错误",与 CYCLIC_DI_DEPENDENCY (-200)PROVIDER_NOT_FOUND (-201)MISSING_INJECTION_CONTEXT (-203) 等同类错误并列。

从报错时机看,NG0204 本质上发生在注入器(Injector)尝试为某个 Token 寻找工厂函数却失败的时刻。核心代码集中在 packages/core/src/di/r3_injector.ts

  • injectableDefOrInjectorDefFactory(token):先通过 getInjectableDef(token) 读取 Token 上的可注入定义(即 ɵprov),取不到再尝试 getFactoryDef(token) 读取工厂定义;
  • 若两者皆为空,且该 Token 是 InjectionToken 实例,则抛出 Token ... is missing a ɵprov definition
  • 若 Token 只是一个普通的 Function(类),则进入 getUndecoratedInjectableFactory(token):此时若构造函数形参个数 token.length > 0,说明它存在 Angular 无法隐式解析的依赖,抛出 Can't resolve all parameters for X: (?, ?, ?)

这两条报错文案分别对应本文后面要讲的两类最常见触发场景。值得注意的一个细节是:报错文案中的 ngDevMode && 前缀表明,这些详细的诊断信息属于 dev 模式输出;在开发构建中看到它们,说明问题正出在 Token 的"可注入性"本身,而不是简单的"找不到 Provider"(后者通常是另一错误码 NG0201 的范畴)。

为什么会出现 NG0204:装饰器与 ɵprov 的关系

Angular 之所以能在构造函数参数没有显式标注类型的情况下自动注入,依赖的是编译期/运行期在每个类上生成的可注入元数据ɵprov)。在 JIT 场景下,这一过程由装饰器编译逻辑完成:

  • @Service() / @Injectable() 装饰器会触发对类型的编译,并把 ɵprov 定义写到类上。以仓库中的 Service 装饰器实现 为例,autoProvided 选项默认为 true,表示该服务会自动暴露给 DI 系统;factory 选项则允许自定义创建实例的方式,二者均会参与生成最终的可注入定义。
  • 对应的 JIT 编译逻辑见 packages/core/src/di/jit/service.tscompileService 会把编译结果(NG_PROV_DEF,即 ɵprov)以属性形式挂载到类型上。
  • packages/core/src/di/jit/util.ts 中,reflectDependencies 通过反射读取构造器参数元数据,并把 @Optional()@SkipSelf()@Inject(TOKEN) 等装饰器参数翻译成 DI 的依赖元数据——这正是"Angular 依赖 TypeScript 元数据推断参数类型"这一说法的实现位置。

理解了这条链路,就能明白两条核心结论:

  1. 缺少装饰器的类没有 ɵprov,因此当它恰好又有构造器依赖时,Angular 无法得知如何创建它,于是抛出 NG0204;
  2. InjectionToken 本身只是描述符,若创建时没有附带工厂函数、又没有在任何注入器上注册 Provider,它同样没有 ɵprov,一旦被注入就报 NG0204。

关于 inject() 函数需要单独说明(官方文档中的 NOTE 也强调过):inject() 接收的是显式 Token,因此"构造器参数类型无法推断"这种问题不会直接发生在它身上。但若 inject() 所注入的那个类自身缺少装饰器、且带有自己的构造器依赖,错误依然会出现——本质没变,只是"出错的那个类"换了个位置。

常见触发场景一:类缺少 @Service()(或 @Injectable())装饰器

这是 NG0204 出现频率最高的一种情形:类本身需要依赖注入,却忘了加装饰器。例如:

export class UserClient {
  // Angular 无法解析 http —— UserClient 上没有可注入元数据(ɵprov)
  constructor(private http: HttpClient) {}
}

此时 Angular 读取不到 UserClient 的任何可注入定义,只能退回到 getUndecoratedInjectableFactory,发现构造器存在形参(http),于是抛出 Can't resolve all parameters for UserClient: (?, ?)

修复方式就是补上装饰器:

@Service()
export class UserClient {
  constructor(private http: HttpClient) {}
}

装饰器生效后,类上被写入了 ɵprov 定义,注入器即可正常解析其构造器依赖。根据本仓库现状,@Service() 已被设计为公开 API(其 autoProvided 默认为 true,会自动将服务暴露给 DI 系统),@Injectable() 同样是本仓库官方错误文档中认可的等价方案。

常见触发场景二:InjectionToken 缺少 Provider 定义

InjectionToken 用来为"非类类型"的依赖(如接口、字符串、配置对象)提供类型安全的 DI Token。但 Token 本身只是一把"钥匙",必须配有可注入定义或 Provider 才能真正被解析。若只创建了 Token 却没有提供值,一旦被注入就会出现错误 Token X is missing a ɵprov definition

错误示例:

// 只有描述,没有 factory,也没有在任何地方 provide —— 被注入时即报 NG0204
export const APP_CONFIG = new InjectionToken<AppConfig>('app config');

两种标准修复方式:

方式一:在使用处注册 Provider(值为某个具体的值):

import {InjectionToken} from '@angular/core';

export const API_URL = new InjectionToken<string>('api url');

// 在组件/指令/模块的 providers 中注册:
// {provide: API_URL, useValue: 'https://api.example.com'}

方式二:在创建 Token 时直接给出默认工厂函数。查看 InjectionToken 实现 可以发现,当构造参数传入 factory 时,其 providedIn 会默认落到 'root',即 Token 自带根级可注入定义:

export const API_URL = new InjectionToken<string>('api url', {
  factory: () => 'https://api.example.com',
});

从排查与复用角度还应当注意 Token 的唯一性:Angular 用 Token 对象本身的引用(而非其描述字符串)来匹配 Provider 与注入点。若在多个文件中分别创建了描述相同但对象不同的 InjectionToken,即使描述完全一致也无法互相解析——这一节在 DI 排查与故障处理指南中有更完整的讨论,建议将 Token 统一导出到共享文件中,避免重复实例化。

常见触发场景三:构造器参数本身不可解析

第三种典型情况是:类已经带有装饰器,但其某个构造器参数类型不是可解析的 DI Token。例如参数引用的是一个既未注册 Provider、也不是可注入类的类型:

@Service()
export class DataStore {
  // Angular 无法解析 config:AppConfig 没有任何 Provider,也不可注入
  constructor(private config: AppConfig) {}
}

这里的 AppConfig 可能是一个纯 TypeScript 接口(接口在运行时会被擦除,没有实体可作为 Token),也可能是一个忘记注册为 Provider 的普通类。接口类型本身没有运行时标识,无法作为 DI Token 使用;若确需按接口注入,业界通行做法就是改配 InjectionToken(见上一节)。

修复需要遵循两条原则:

  1. 所有构造器参数要么是可注入类(带装饰器、有 ɵprov),要么已在某层注入器注册了对应 Provider
  2. 确实可能缺失的依赖应显式声明为可选,例如给参数标注 @Optional(),或结合 @Inject() 指定显式 Token。

对可选依赖使用 @Optional() 可以让 Angular 在找不到 Provider 时注入 null 而不是抛错,从而把"缺失依赖"从编译期/启动期错误降级为可运行的业务逻辑分支。

读懂报错文案与调用栈

NG0204 的报错信息通常直接点明问题 Token,两种主要文案及其含义对照如下:

报错文案 含义与处理方向
Can't resolve all parameters for X: (?, ?, ?) X 缺少装饰器或其依赖参数无法被推断。? 占位符的数量等于无法解析的形参个数。检查类是否带有 @Service() / @Injectable(),并确保每个形参类型都有 Provider。
Token X is missing a ɵprov definition X 是一个没有可注入定义的 InjectionToken(或装饰器未生效的类 Token)。用 {provide: TOKEN, useValue: ...} 注册,或在 Token 定义上补充默认 factory

注意第一行文案中的 ? 数量与构造器形参个数一一对应——它由 getUndecoratedInjectableFactory 中用 newArray(paramLength, '?') 生成(见 r3_injector.ts 中的实现),paramLength 来自 JS 函数的 length 属性。若类本身没有构造器参数,Angular 甚至可以不依赖装饰器直接实例化(无参函数场景被单独放行),这也解释了为什么"报错的类总是恰好带构造器依赖"。

系统化排查清单

拿到报错后,可以按下面顺序从调用栈自底向上排查,定位到发生注入的位置后逐项核对:

  1. 该类是否带有 @Service() / @Injectable() 装饰器——缺失则补齐,并确认没有被误删或从错误的模块导入;
  2. 构造器每个形参是否都有对应 Provider——检查形参类型是否是已注册的可注入类,或是否在组件/指令/forRoot()/providers 等处配置了对应提供者;
  3. 每个 InjectionToken 是否已有 Provider 或默认值——要么在使用侧配置 {provide: TOKEN, useValue: ...} / useFactory / useClass,要么在创建 Token 时提供 factory 默认值;
  4. Token 是否唯一——确认是从共享文件统一导入,而不是在多个地方各自 new InjectionToken(...)
  5. 确属可选的依赖是否加了 @Optional()——避免本可优雅降级为 null 的依赖变成硬错误。

仓库内的相关单元测试覆盖了上述行为(如 packages/core/test/di/r3_injector_spec.tspackages/core/test/linker/ng_module_integration_spec.ts 中均涉及 NG0204 / missing a ɵprov definition 的用例),当你修改相关实现时,这些测试也是验证行为是否回归的可靠参考。此外,官方错误索引 errors/overview.md 收录了本错误的完整清单,DI 实战侧的补充建议可继续阅读 debugging-and-troubleshooting-di.md 的 "InjectionToken issues" 小节;本文的直接依据文档为 NG0204.md

小结

NG0204 不是偶发的随机故障,它的出现几乎总是意味着某个 DI Token(类或 InjectionToken)缺少"可被注入系统认识的元数据"。把握住三个要点即可系统性地消灭此类错误:类务必携带装饰器、Token 务必有 Provider 或默认工厂、构造器形参务必可解析。而一旦报错出现,先读文案(是 Can't resolve all parameters 还是 missing a ɵprov definition),再顺调用栈找到注入发生处,配合本文清单逐项核对,通常几分钟内就能锁定并修复问题。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
898
5.82 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
921
1.84 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.8 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
531
596
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.02 K
519
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
391