尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Angular 注入上下文(Injection Context):inject() 的可用时机、runInInjectionContext 与 assertInInjectionContext 详解

Angular 注入上下文(Injection Context):inject() 的可用时机、runInInjectionContext 与 assertInInjectionContext 详解 Angular 注入上下文Injection Contextinject() 的可用时机、runInInjectionContext 与 assertInInjectionContext 详解【免费下载链接】angularDeliver web apps with confidence 项目地址: https://gitcode.com/GitHub_Trending/an/angular在 Angular 的依赖注入DI系统中inject()函数并不是在任何地方都能调用的——它依赖一个“当前注入器可用”的运行时上下文即注入上下文Injection Context。本文基于 Angular 官方文档 Injection context 展开系统讲解哪些代码位置天然处于注入上下文、如何通过runInInjectionContext主动创建上下文、如何用assertInInjectionContext编写可复用的注入辅助函数并结合angular/core的源码剖析上下文切换的底层机制与 NG0203 错误的触发路径帮助你在自定义 API、路由守卫和工具函数中安全地使用函数式注入。注入上下文是什么inject()生效的五个时机Angular 的 DI 系统依赖一个运行时上下文在其中当前的注入器injector是可访问的。这意味着注入器只在你于该上下文内执行代码时才正常工作。文档明确列出了五个拥有注入上下文的情形在 DI 系统实例化的类构造期间通过constructor例如Injectable或Component的类在这类类的字段初始化器field initializers中在Provider或Injectable的useFactory指定的工厂函数中在InjectionToken的factory函数中在处于注入上下文中的栈帧内执行时即调用链上游某处已经进入上下文当前函数帧仍可注入。知道当前是否处于注入上下文决定了你能否使用inject函数来获取依赖。对于在构造函数与字段初始化器中使用inject()的基础示例可参见 DI 概览指南中“where can inject be used”一节位于 adev/src/content/guide/di/ 目录下。源码视角inject()如何定位“当前上下文”从源码结构看注入上下文的本质是一个“当前注入器”的全局槽位。packages/core/src/di/injector_compatibility.ts 中的公开inject()实现非常简洁export function injectT(token: ProviderTokenT | HostAttributeToken, options?: InjectOptions) { return ɵɵinject(token as any, convertToBitFlags(options)); }它把调用转给编译器指令ɵɵinject同文件 L136-L148后者优先使用通过 packages/core/src/di/inject_switch.ts 设置的注入实现渲染引擎内部会切换为带NodeInjector感知的版本否则回退到injectInjectorOnly。关键的判定逻辑在 injectInjectorOnly 中若getCurrentInjector()返回undefined完全无上下文抛出 NG0203 错误若返回null进入“limp mode”跛行模式只能解析providedIn: root的可注入令牌见 injectRootLimpMode否则通过currentInjector.retrieve(token, options)完成解析optional标志未命中时返回null。这就解释了文档中“注入器只在你执行代码于该上下文内时才工作”这一论断的底层原理上下文 一个被正确设置的getCurrentInjector()返回值。处于上下文中的栈帧路由守卫中的典型用法某些 API 被设计为“在注入上下文中运行”路由守卫就是典型例子——这使得你可以在守卫函数内部直接使用inject()访问服务而不必通过守卫函数的参数传递。文档以CanActivateFn为例const canActivateTeam: CanActivateFn ( route: ActivatedRouteSnapshot, state: RouterStateSnapshot, ) { return inject(PermissionsService).canActivate(inject(UserToken), route.params.id); };这里守卫回调的整个栈帧都被 Angular Router 包在了注入上下文中执行因此inject(PermissionsService)与inject(UserToken)都能正常解析。同一原理同样适用于CanMatchFn、ResolveFn等由框架保证在注入上下文中调用的函数式 API只要框架执行你的回调前设置了当前注入器回调内的任意调用链同一栈帧内同步执行的代码都可以调用inject()。主动进入上下文runInInjectionContext当你在方法、事件处理器或异步回调里需要注入但当前并不处于注入上下文时可以使用runInInjectionContext。它需要拿到一个注入器如EnvironmentInjector。文档给出的示例// hero.service.ts Injectable({ providedIn: root, }) export class HeroService { private environmentInjector inject(EnvironmentInjector); someMethod() { runInInjectionContext(this.environmentInjector, () { inject(SomeService); // Do what you need with the injected service }); } }注意文档强调的返回语义inject()只有在注入器能够解析请求的令牌时才返回实例解析失败且非optional时抛出错误。底层实现上下文是怎么“切换”又“还原”的runInInjectionContext的公开 API 定义在 packages/core/src/di/contextual.ts并通过 packages/core/src/di/index.ts 对外导出。其实现是一个标准的“保存旧值 → 设置新值 → try/finally 恢复”结构export function runInInjectionContextReturnT(injector: Injector, fn: () ReturnT): ReturnT { let internalInjector: PrimitivesInjector; if (injector instanceof R3Injector) { assertNotDestroyed(injector); // 注入器已销毁则报错 internalInjector injector; } else { internalInjector new RetrievingInjector(injector); // 包装非 R3Injector 的 Injector } let prevInjectorProfilerContext: InjectorProfilerContext; if (ngDevMode) { prevInjectorProfilerContext setInjectorProfilerContext({injector, token: null}); } const prevInjector setCurrentInjector(internalInjector); // ① 设置当前注入器 const previousInjectImplementation setInjectImplementation(undefined); try { return fn(); // ② 在上下文中执行闭包 } finally { setCurrentInjector(prevInjector); // ③ 恢复上一个注入器 ngDevMode setInjectorProfilerContext(prevInjectorProfilerContext!); setInjectImplementation(previousInjectImplementation); } }有三个从源码可以确认的工程细节值得注意上下文是同步的API 文档注释明确说明inject只能同步使用不能在异步回调或任何await点之后使用——因为上下文的“进入/退出”依赖调用栈的进入/退出而不是 Promise 链。状态总是被还原无论fn()成功还是抛错finally都会恢复前一个注入器与注入实现保证外层上下文不受影响。支持任意Injector如果传入的不是R3Injector实例会被包进 RetrievingInjector 适配器转调旧版Injector.get()接口因此任何实现了Injector接口的对象都能作为上下文来源。另外isInInjectionContext 提供了无副作用的探测函数判断依据是“当前存在注入实现或当前注入器非空”可用于在自定义代码中先探测、再分支处理。断言上下文assertInInjectionContext与可复用的注入辅助函数Angular 提供assertInInjectionContext辅助函数用于校验当前上下文是否为注入上下文并在不是时抛出清晰错误。使用时应传入调用函数的引用让错误信息指向正确的 API 入口从而得到比默认通用注入错误更清晰、更可操作的消息。文档示例import {ElementRef, assertInInjectionContext, inject} from angular/core; export function injectNativeElementT extends Element(): T { assertInInjectionContext(injectNativeElement); return inject(ElementRef).nativeElement; }随后这个辅助函数必须从注入上下文中调用构造函数、字段初始化器、Provider 工厂或经runInInjectionContext执行的代码import {Component, inject} from angular/core; import {injectNativeElement} from ./dom-helpers; Component({ /* … */ }) export class PreviewCard { readonly hostEl injectNativeElementHTMLElement(); // 字段初始化器处于注入上下文中可用 onAction() { const anotherRef injectNativeElementHTMLElement(); // 会失败运行在注入上下文之外 } }字段初始化器在实例化期间执行因此合法而onAction()作为事件处理普通方法运行时DI 上下文早已结束调用会抛错。源码视角为什么传函数引用而不是字符串查看 assertInInjectionContext 的实现export function assertInInjectionContext(debugFn: Function): void { // Taking a Function instead of a string name here prevents the unminified name of the function // from being retained in the bundle regardless of minification. if (!isInInjectionContext()) { throw new RuntimeError( RuntimeErrorCode.MISSING_INJECTION_CONTEXT, ngDevMode debugFn.name () can only be used within an injection context such as a constructor, a factory function, a field initializer, or a function used with runInInjectionContext, ); } }源码注释解释了设计取舍接收Function而非字符串名称是为了避免未压缩的函数名在压缩minification后仍被保留在 bundle 中——函数名随压缩器统一重命名/剔除包体积更友好。同时错误详情仅在ngDevMode下附加生产环境的RuntimeError只携带错误码MISSING_INJECTION_CONTEXT这正是文档中 error NG0203 的来源。在上下文之外使用 DING0203 错误的完整链路当你在注入上下文之外调用inject()或assertInInjectionContext时Angular 抛出错误NG0203。从源码可以完整还原这条错误链路错误码定义在 packages/core/src/errors.tsMISSING_INJECTION_CONTEXT -203负号是 Angular 运行时错误码的编码约定展示时转为 NG0203触发点有两个assertInInjectionContext的主动断言contextual.ts L80-L87以及inject()本身在getCurrentInjector() undefined时的兜底抛出injector_compatibility.ts L98-L103后者附带 devMode 提示inject()必须从构造函数、工厂函数、字段初始化器或runInInjectionContext包裹的函数中调用官方验收测试在 packages/core/test/acceptance/di_spec.ts 中断言该错误码e instanceof RuntimeError e.code RuntimeErrorCode.MISSING_INJECTION_CONTEXT确认了这一行为是契约级的。由此可以总结出几个高频踩坑点生命周期钩子里调用inject()会报错ngOnInit等钩子在实例构造完成后才执行此时注入上下文已结束inject的 API 注释中专门给出了CarComponent.ngOnInit中inject(Engine)的反例事件处理器、setTimeout回调、订阅回调中调用inject()会报错这些回调运行在 DI 系统之外的普通调用栈上跨await使用inject()不可靠await之后代码已离开原来的同步栈帧即便原栈帧在上下文中修复模式在构造函数/字段初始化器中先inject(EnvironmentInjector)持有引用需要时再用runInInjectionContext包裹调用或者把依赖作为构造参数/方法参数显式传递。小结API作用源码位置inject(token, options?)从当前注入上下文解析依赖injector_compatibility.tsrunInInjectionContext(injector, fn)以给定注入器为上下文同步执行fncontextual.tsisInInjectionContext()探测当前是否处于注入上下文contextual.tsassertInInjectionContext(fn)断言上下文失败抛 NG0203contextual.ts掌握注入上下文的边界是正确使用 Angular 函数式注入的前提在构造函数、字段初始化器、useFactory/factory以及框架保证在上下文内运行的回调如路由守卫中直接使用inject()在其余场景用runInInjectionContext显式建立上下文为封装注入逻辑的公共函数加上assertInInjectionContext把含糊的运行时错误转化为指向 API 入口的明确报错。【免费下载链接】angularDeliver web apps with confidence 项目地址: https://gitcode.com/GitHub_Trending/an/angular创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表