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)。文档给出了如下选型速查表:
| 功能 / 需求 | @Service | @Injectable |
|---|---|---|
inject()函数支持 | 是 | 是 |
| 构造函数依赖注入(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?: Record<string, unknown>) { // No-op by default. } } class GoogleAnalytics extends Analytics { override track(event: string, payload?: Record<string, unknown>) { // Dispatches an analytics event to Google Analytics } }注意:factory选项取代了@Injectable中的useClass、useValue、useExisting与useFactory选项;如果确实需要其中任何一项,请继续使用@Injectable。
退出自动提供(autoProvided: false)
默认情况下@Service把类挂在根注入器上。如果希望手动提供该服务(例如将其作用域限定到某个路由或组件),设置autoProvided: false:
import {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:ɵprov(provider 定义)与ɵfac(工厂定义,deps为空、target: FactoryTarget.Service)。注意 JIT 路径同样不接受构造函数依赖(reflectDependencies仅用于反射场景,工厂目标固定为 Service)——这与 AOT 诊断保持一致。
3. AOT 编译路径。ServiceDecoratorHandler 在分析阶段提取装饰器元数据(autoProvided必须是布尔字面量,否则报VALUE_HAS_WRONG_TYPE;参数最多一个对象字面量,否则报DECORATOR_ARITY_WRONG),编译阶段生成ɵfac与ɵprov静态字段,并禁止类上已存在静态ɵprov(INJECTABLE_DUPLICATE_PROV)。
4. 行为验证测试。验收测试 packages/core/test/acceptance/service_spec.ts 覆盖了文档所述全部关键行为:
factory可以提供替代实现,且实例值来自工厂返回值(L28-L56);- 工厂函数内部可以
inject()其他 provider(L58-L82); autoProvided: false时自动注入会抛出No provider found for 'MyService'错误(L84-L94);autoProvided: false的服务可以通过providers: [{provide: MyService, useValue: ...}]手动提供(L96-L120);@Service可被useClass覆盖,即使原服务定义了factory,覆盖依然生效(L122-L182);- 服务会正确执行
ngOnDestroy生命周期钩子(L184-L205)。
进阶方向
providedIn: 'root'覆盖了大多数使用场景,但 Angular 还提供了面向更专门场景的配置方式:
- 组件级实例(Component-specific instances)——当组件需要各自隔离的服务实例时;
- 手动配置(Manual configuration)——面向需要运行时配置的服务;
- 工厂 provider(Factory providers)——根据运行时条件动态创建服务;
- 值 provider(Value providers)——提供配置对象或常量。
这些高级模式可以继续参阅 defining-dependency-providers.md;理解factory中为什么能调用inject(),则建议先阅读依赖注入上下文文档。
【免费下载链接】angularDeliver web apps with confidence 🚀项目地址: https://gitcode.com/GitHub_Trending/an/angular
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考