Angular 服务创建与使用实战指南:@Service 装饰器、根级提供机制与源码级原理剖析
Angular 服务创建与使用实战指南Service 装饰器、根级提供机制与源码级原理剖析【免费下载链接】angularDeliver web apps with confidence 项目地址: https://gitcode.com/GitHub_Trending/an/angular本篇技术指南围绕 Angular 仓库中的官方文档 creating-and-using-services.md 展开系统讲解如何用ng generate service或Service()装饰器创建服务、理解根级root单例提供机制、通过factory替换实现以及用inject()在组件和服务之间互相注入。读完本篇你不仅掌握服务定义与注入的完整实战流程还能从 Angular 源码层面看清Service装饰器是如何被编译为ɵprov定义、为何不支持构造函数依赖注入以及每一项行为约束背后的编译期诊断与测试依据。什么是服务为什么要用服务服务Service是可以跨 Angular 应用共享的、可复用的代码单元。文档给出的典型用途有三类数据获取data fetching、业务逻辑business logic、以及其他多个组件都需要访问的功能。把这类横切能力从组件中抽离为服务可以让多个组件共享同一份状态或行为避免在组件里重复实现。创建服务使用 Angular CLI 生成最快的方式是通过 Angular CLI 命令生成ng generate service CUSTOM_NAME该命令会在你的src目录中创建一个独立的CUSTOM_NAME.ts文件作为服务的定义文件。手动创建为类添加 Service() 装饰器也可以手动创建服务——在 TypeScript 类上添加Service()装饰器即可。这个装饰器告诉 Angular该类可以作为可注入的依赖injectable dependency使用。文档给出的示例定义了一个允许用户添加和读取数据的服务import {Service} from angular/core; Service() export class BasicDataStore { private data: string[] []; addData(item: string): void { this.data.push(item); } getData(): string[] { return [...this.data]; } }从源码看BasicDataStore内部用私有数组保存数据getData()返回浅拷贝[...this.data]而不是内部数组引用避免调用方意外修改服务内部状态——这是一个值得借鉴的防御式写法。服务如何变为可用根级提供服务默认在根级别root level提供。当一个服务被全局提供时Angular 保证三个核心收益单例Singleton Instance整个应用共享同一个实例全局可用Global Availability无需手动注册 provider任意位置均可访问可摇树优化Tree-shakability如果代码中从未显式使用它该服务会被排除出最终的生产打包产物。这三点直接对应了Service在底层被编译为providedIn: root语义这一事实。在 packages/core/src/di/interface/service.ts 中可以看到运行时定义函数ɵɵdefineService的关键一行providedIn: opts.autoProvided false ? null : root,也就是说除非显式关闭自动提供Service类一律被挂到根注入器上——这就是“默认根级单例”的实现来源。Service 与 Injectable 装饰器对比Service是传统Injectable({ providedIn: root })语法的现代、简洁替代品ergonomic shorthand。文档给出了如下选型速查表功能 / 需求ServiceInjectableinject()函数支持是是构造函数依赖注入Constructor-based DI否是隐式根级单例提供是否需要{providedIn: root}高级 provider 键useClass等否是自定义初始化工厂是是非根作用域platform等否是表中“Service不支持构造函数注入”这条并非口头约定而是由编译器强制的。在 AOT 编译处理器 packages/compiler-cli/src/ngtsc/annotations/src/service.ts 中ServiceDecoratorHandler会检查类含基类链的构造函数参数一旦发现参数依赖就抛出诊断Service class cannot use constructor dependency injection. Use the inject function instead.同文件 analyze 阶段 还会检查装饰器冲突一个类上不能同时挂Service和另一个 Angular 装饰器DECORATOR_COLLISION诊断。这两处源码印证了文档表格中Service的能力边界。用 factory 替换服务的实现当需要控制单例的创建方式——例如按环境切换不同实现——可以传入factory函数。工厂运行在注入上下文injection context中因此可以在内部用inject()读取其他依赖。文档中的Analytics示例本地环境下是 no-op避免事件污染开发控制台生产环境中工厂读取ANALYTICS_ENABLEDtoken返回把事件转发给真实追踪器的GoogleAnalytics子类import {inject, InjectionToken, Service} from angular/core; import {ANALYTICS_ENABLED} from ./token; Service({ factory: () (inject(ANALYTICS_ENABLED) ? new GoogleAnalytics() : new Analytics()), }) export class Analytics { track(event: string, payload?: Recordstring, unknown) { // No-op by default. } } class GoogleAnalytics extends Analytics { override track(event: string, payload?: Recordstring, unknown) { // Dispatches an analytics event to Google Analytics } }注意factory选项取代了Injectable中的useClass、useValue、useExisting与useFactory选项如果确实需要其中任何一项请继续使用Injectable。退出自动提供autoProvided: false默认情况下Service把类挂在根注入器上。如果希望手动提供该服务例如将其作用域限定到某个路由或组件设置autoProvided: falseimport {Service} from angular/core; Service({autoProvided: false}) export class AnalyticsLogger { trackEvent(name: string) { console.log(event:, name); } }此后你需要像对待普通Injectable()一样自行把服务加入某个providers数组。这一语义在源码 packages/core/src/di/interface/service.ts 中体现为providedIn: null——即不再自动挂到任何作用域。何时选 Service何时选 Injectable文档的决策建议当你创建一个新的单例类、且依赖通过inject()获取时直接用Service出现以下任一需求时保留Injectable构造函数依赖注入——Service只支持inject()函数高级 provider 配置如useClass、useValue、useExisting、useFactory——Service只暴露单一的factory选项非根作用域如providedIn: platform。注入服务用providedIn: root或Service默认行为创建服务后可以在应用任意位置使用angular/core的inject()函数注入它。注入到组件import {Component, inject} from angular/core; import {BasicDataStore} from ./basic-data-store; Component({ selector: app-example, template: div p{{ dataStore.getData() }}/p button (click)dataStore.addData(More data)Add more data/button /div , }) export class Example { dataStore inject(BasicDataStore); }组件把注入点声明为字段初始化表达式dataStore inject(BasicDataStore)模板中即可直接调用getData()/addData()。服务注入另一个服务import {inject, Service} from angular/core; import {AdvancedDataStore} from ./advanced-data-store; Service() export class BasicDataStore { private advancedDataStore inject(AdvancedDataStore); private data: string[] []; addData(item: string): void { this.data.push(item); } getData(): string[] { return [...this.data, ...this.advancedDataStore.getData()]; } }服务之间通过字段级inject()互相组合getData()把本地数据与AdvancedDataStore的数据合并返回。源码级原理Service 的编译产物上面所有行为的底层机制可以从仓库源码中得到印证1. 装饰器声明。packages/core/src/di/service.ts 定义了ServiceDecorator的四个重载无参Service()、{autoProvided: false}、{autoProvided?, factory}与{autoProvided?: true}。Service元数据接口只有两个字段autoProvided默认true与factory零参工厂函数。装饰器最终通过makeDecorator注册运行时委托给compileService。2. 运行时 JIT 路径。packages/core/src/di/jit/service.ts 中的compileService为类动态定义两个惰性 getterɵprovprovider 定义与ɵfac工厂定义deps为空、target: FactoryTarget.Service。注意 JIT 路径同样不接受构造函数依赖reflectDependencies仅用于反射场景工厂目标固定为 Service——这与 AOT 诊断保持一致。3. AOT 编译路径。ServiceDecoratorHandler 在分析阶段提取装饰器元数据autoProvided必须是布尔字面量否则报VALUE_HAS_WRONG_TYPE参数最多一个对象字面量否则报DECORATOR_ARITY_WRONG编译阶段生成ɵfac与ɵprov静态字段并禁止类上已存在静态ɵprovINJECTABLE_DUPLICATE_PROV。4. 行为验证测试。验收测试 packages/core/test/acceptance/service_spec.ts 覆盖了文档所述全部关键行为factory可以提供替代实现且实例值来自工厂返回值L28-L56工厂函数内部可以inject()其他 providerL58-L82autoProvided: false时自动注入会抛出No provider found for MyService错误L84-L94autoProvided: false的服务可以通过providers: [{provide: MyService, useValue: ...}]手动提供L96-L120Service可被useClass覆盖即使原服务定义了factory覆盖依然生效L122-L182服务会正确执行ngOnDestroy生命周期钩子L184-L205。进阶方向providedIn: root覆盖了大多数使用场景但 Angular 还提供了面向更专门场景的配置方式组件级实例Component-specific instances——当组件需要各自隔离的服务实例时手动配置Manual configuration——面向需要运行时配置的服务工厂 providerFactory providers——根据运行时条件动态创建服务值 providerValue providers——提供配置对象或常量。这些高级模式可以继续参阅 defining-dependency-providers.md理解factory中为什么能调用inject()则建议先阅读依赖注入上下文文档。【免费下载链接】angularDeliver web apps with confidence 项目地址: https://gitcode.com/GitHub_Trending/an/angular创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考