08_服务与依赖注入(DI)系统
服务与依赖注入(DI)系统
服务是Angular应用中封装可复用业务逻辑的核心机制,而依赖注入(Dependency Injection,简称DI)则是Angular实现组件与服务解耦的关键技术。通过DI系统,Angular能够自动创建和提供依赖对象,使组件专注于UI逻辑,服务专注于业务逻辑,大幅提升代码的可维护性和可测试性。本章将深入讲解Angular v20中服务的创建方式、DI系统的实战应用以及版本优化。
1. 服务创建:@Injectable装饰器与提供商配置
服务在Angular中通常是一个带有@Injectable装饰器的类,用于封装与特定功能相关的方法和数据。@Injectable装饰器标记一个类可以被注入器实例化并注入到其他组件或服务中。
1.1 基本服务创建
创建一个基础服务的步骤:
// user.service.ts
import { Injectable } from '@angular/core';
import { User } from './user.model';
// @Injectable装饰器标记这是一个可注入的服务
@Injectable({
providedIn: 'root' // 提供商配置:在根注入器中提供此服务
})
export class UserService {
private currentUser?: User;
// 登录方法
login(username: string, password: string): Promise<User> {
// 模拟API调用
return new Promise((resolve) => {
setTimeout(() => {
this.currentUser = { id: 1, name: username, email: `${username}@example.com` };
resolve(this.currentUser);
}, 1000);
});
}
// 获取当前用户
getCurrentUser(): User | undefined {
return this.currentUser;
}
// 登出方法
logout(): void {
this.currentUser = undefined;
}
}
@Injectable装饰器的providedIn属性指定了服务的提供商范围:
'root':服务在整个应用中是单例,由根注入器管理'platform':服务在多个应用共享的平台级别提供- 特定模块名称:如
providedIn: UserModule,服务仅在该模块中可用
1.2 提供商配置方式
除了使用@Injectable的providedIn属性,还可以通过providers数组配置服务提供商,有以下几种常见方式:
1.2.1 类提供商(Class Provider)
最常用的提供商形式,直接指定服务类:
import { NgModule } from '@angular/core';
import { UserService } from './user.service';
@NgModule({
providers: [UserService] // 等同于 { provide: UserService, useClass: UserService }
})
export class UserModule { }
1.2.2 别名提供商(Alias Provider)
为现有服务创建别名,便于注入:
import { NgModule } from '@angular/core';
import { UserService, AuthService } from './auth.services';
@NgModule({
providers: [
UserService,
{ provide: AuthService, useExisting: UserService } // AuthService是UserService的别名
]
})
export class AuthModule { }
1.2.3 值提供商(Value Provider)
直接提供一个值作为服务,常用于常量或配置:
import { NgModule } from '@angular/core';
const API_CONFIG = {
baseUrl: 'https://api.example.com',
timeout: 5000
};
@NgModule({
providers: [
{ provide: 'API_CONFIG', useValue: API_CONFIG }
]
})
export class CoreModule { }
使用时通过注入令牌'API_CONFIG'获取:
import { Component, Inject } from '@angular/core';
@Component({
selector: 'app-data-fetcher',
standalone: true,
template: `<p>API地址:{{ apiConfig.baseUrl }}</p>`
})
export class DataFetcherComponent {
constructor(@Inject('API_CONFIG') public apiConfig: any) {}
}
1.2.4 工厂提供商(Factory Provider)
通过工厂函数动态创建服务实例,适合需要根据条件创建不同服务实例的场景:
import { NgModule, Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
// 定义日志服务接口
interface LoggerService {
log(message: string): void;
}
// 控制台日志实现
@Injectable()
class ConsoleLogger implements LoggerService {
log(message: string) {
console.log(`[LOG]: ${message}`);
}
}
// 远程日志实现
@Injectable()
class RemoteLogger implements LoggerService {
constructor(private http: HttpClient) {}
log(message: string) {
this.http.post('/api/logs', { message }).subscribe();
}
}
// 工厂函数:根据环境决定使用哪种日志服务
function loggerFactory(http: HttpClient, environment: any) {
if (environment.production) {
return new RemoteLogger(http);
} else {
return new ConsoleLogger();
}
}
@NgModule({
providers: [
{
provide: LoggerService,
useFactory: loggerFactory,
deps: [HttpClient, 'ENVIRONMENT'] // 工厂函数依赖
},
{ provide: 'ENVIRONMENT', useValue: { production: false } }
]
})
export class LoggerModule { }
2. DI实战场景
依赖注入在Angular应用中无处不在,从简单的组件依赖到复杂的跨模块服务共享,掌握不同场景下的DI使用方法是构建可维护应用的关键。
2.1 组件级依赖:providers数组声明
当服务只需要在特定组件及其子组件中使用时,可以在组件的providers数组中声明,实现组件级别的依赖隔离。
import { Component } from '@angular/core';
import { UserService } from './user.service';
@Component({
selector: 'app-user-profile',
standalone: true,
providers: [UserService], // 组件级服务,每个组件实例拥有独立的服务实例
template: `
<button (click)="loadUser()">加载用户</button>
@if (user) {
<p>姓名:{{ user.name }}</p>
}
`
})
export class UserProfileComponent {
user?: any;
// 注入组件级UserService
constructor(private userService: UserService) {}
loadUser() {
this.userService.login('testuser', 'password')
.then(user => this.user = user);
}
}
组件级服务的特点:
- 每个组件实例会创建一个新的服务实例
- 服务在组件销毁时也会被销毁
- 子组件可以继承并覆盖父组件的服务提供商
2.2 应用级依赖:provideHttpClient等全局服务
对于需要在整个应用中共享的服务(如HTTP客户端、认证服务),应配置为应用级依赖。Angular v20提供了provideXxx函数简化全局服务配置。
2.2.1 HTTP客户端服务
Angular的HttpClientModule已被provideHttpClient函数替代,用于配置全局HTTP服务:
// main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { provideHttpClient } from '@angular/common/http'; // 导入HTTP服务提供商
import { provideZonelessChangeDetection } from '@angular/core';
bootstrapApplication(AppComponent, {
providers: [
provideHttpClient(), // 配置全局HTTP服务
provideZonelessChangeDetection()
]
}).catch(err => console.error(err));
使用HTTP服务:
import { Component, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Component({
selector: 'app-data-loader',
standalone: true,
template: `
<button (click)="fetchData()">加载数据</button>
@if (data) {
<pre>{{ data | json }}</pre>
}
`
})
export class DataLoaderComponent {
// 使用inject函数注入HTTP服务(替代构造函数注入)
private http = inject(HttpClient);
data?: any;
fetchData() {
this.http.get('https://api.example.com/data')
.subscribe(data => this.data = data);
}
}
2.2.2 路由服务配置
路由服务也是典型的应用级依赖,通过provideRouter配置:
// app.routes.ts
import { Routes } from '@angular/router';
import { HomeComponent } from './home.component';
import { AboutComponent } from './about.component';
export const routes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'about', component: AboutComponent }
];
// main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
bootstrapApplication(AppComponent, {
providers: [
provideRouter(routes) // 配置路由服务
]
});
2.2.3 全局状态服务
创建全局状态服务管理应用级状态:
// app-state.service.ts
import { Injectable, signal } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class AppStateService {
// 使用Signal存储全局状态
theme = signal<'light' | 'dark'>('light');
notifications = signal<number>(0);
// 切换主题
toggleTheme() {
this.theme.update(current => current === 'light' ? 'dark' : 'light');
}
// 增加通知计数
incrementNotifications() {
this.notifications.update(count => count + 1);
}
// 清除通知
clearNotifications() {
this.notifications.set(0);
}
}
在组件中使用全局状态服务:
import { Component, inject } from '@angular/core';
import { AppStateService } from './app-state.service';
@Component({
selector: 'app-header',
standalone: true,
template: `
<div [class.dark-theme]="theme() === 'dark'">
<button (click)="toggleTheme()">切换主题</button>
<div class="notifications">
通知:{{ notifications() }}
<button (click)="clearNotifications()">清除</button>
</div>
</div>
`,
styles: [`
.dark-theme { background: #333; color: white; }
.notifications { margin-left: 20px; }
`]
})
export class HeaderComponent {
private appState = inject(AppStateService);
// 暴露信号供模板使用
theme = this.appState.theme;
notifications = this.appState.notifications;
// 调用服务方法
toggleTheme() {
this.appState.toggleTheme();
}
clearNotifications() {
this.appState.clearNotifications();
}
}
3. v20 DI优化:树摇支持与依赖追踪机制
Angular v20对依赖注入系统进行了多项优化,重点提升了服务的可树摇性(Tree-shakable)和依赖追踪效率,使应用体积更小、启动更快。
3.1 树摇支持(Tree-shakable Providers)
树摇是指打包工具(如Webpack、Rollup)能够移除未使用代码的过程。Angular v20增强了对服务树摇的支持,通过providedIn配置的服务默认具备可树摇性。
// 可树摇的服务
@Injectable({ providedIn: 'root' })
export class AnalyticsService {
trackEvent(event: string) {
console.log('Track event:', event);
}
}
当服务被标记为providedIn: 'root'且未在任何组件或服务中注入时,打包工具会将其从最终bundle中移除,减少应用体积。
注意:通过模块providers数组声明的服务默认不可树摇,因为模块无法确定服务是否被使用。因此,Angular v20推荐优先使用providedIn: 'root'形式。
3.2 依赖追踪机制改进
Angular v20改进了依赖注入的追踪机制,使注入器能够更高效地解析依赖关系,特别是在处理层级注入器时。
3.2.1 注入器层级与依赖解析
Angular的注入器形成一个层级结构,从根注入器到模块注入器再到组件注入器。当请求一个依赖时,注入器会先在自身查找,找不到则向上级注入器查找,直到根注入器。
v20优化了这一查找过程,通过预编译依赖信息,减少了运行时的查找开销。同时,对于独立组件,依赖解析更加直接,无需经过模块注入器层级。
// 根注入器提供的服务
@Injectable({ providedIn: 'root' })
export class LoggingService {
log(message: string) {
console.log('Root logger:', message);
}
}
// 组件注入器提供的服务(覆盖根服务)
@Component({
selector: 'app-special',
standalone: true,
providers: [
{
provide: LoggingService,
useValue: { log: (msg: string) => console.log('Special logger:', msg) }
}
],
template: `<button (click)="logMessage()">Log</button>`
})
export class SpecialComponent {
constructor(private logger: LoggingService) {}
logMessage() {
this.logger.log('Hello from special component'); // 使用组件级服务
}
}
3.2.2 inject()函数与依赖注入上下文
Angular v14引入的inject()函数在v20中得到进一步优化,允许在类的方法中注入依赖,而不仅限于构造函数:
import { Component, inject, OnInit } from '@angular/core';
import { DataService } from './data.service';
@Component({
selector: 'app-lazy-data',
standalone: true,
template: `<p>{{ data || 'Loading...' }}</p>`
})
export class LazyDataComponent implements OnInit {
data?: string;
// 在生命周期钩子中注入服务
ngOnInit() {
const dataService = inject(DataService);
dataService.fetchData().then(data => this.data = data);
}
}
inject()函数只能在"注入上下文"中使用,包括:
- 类的构造函数
- 装饰器的工厂函数
- 生命周期钩子(如ngOnInit)
- 服务或指令的初始化代码
v20增强了inject()的类型检查,确保注入的依赖类型正确,并提供更好的错误提示。
3.3 独立组件中的DI优化
独立组件(standalone: true)在v20中对DI有特别优化,它们不依赖模块,直接在imports数组中声明依赖:
import { Component, inject } from '@angular/core';
import { UserService } from './user.service';
import { ProfileCardComponent } from './profile-card.component';
@Component({
selector: 'app-user-dashboard',
standalone: true,
imports: [ProfileCardComponent], // 直接导入所需组件
providers: [], // 可选:组件级服务
template: `
<h2>用户面板</h2>
<app-profile-card></app-profile-card>
`
})
export class UserDashboardComponent {
private userService = inject(UserService);
// ...
}
独立组件的DI优势:
- 依赖关系更清晰,直接在组件中声明
- 减少模块间接依赖导致的不必要服务加载
- 注入器解析路径更短,性能更好
- 更易于进行单元测试,可精确控制注入的依赖
3.4 服务测试与DI模拟
v20的DI系统改进也使服务测试更加便捷,通过TestBed可以轻松模拟依赖:
import { TestBed } from '@angular/core/testing';
import { UserService } from './user.service';
import { HttpClient } from '@angular/common/http';
describe('UserService', () => {
let service: UserService;
let httpMock: jasmine.SpyObj<HttpClient>;
beforeEach(() => {
// 创建HTTP客户端的模拟对象
const mockHttp = jasmine.createSpyObj('HttpClient', ['post']);
TestBed.configureTestingModule({
providers: [
UserService,
{ provide: HttpClient, useValue: mockHttp }
]
});
service = TestBed.inject(UserService);
httpMock = TestBed.inject(HttpClient) as jasmine.SpyObj<HttpClient>;
});
it('should login user', async () => {
// 模拟HTTP响应
httpMock.post.and.returnValue(Promise.resolve({ id: 1, name: 'Test' }));
const user = await service.login('test', 'pass');
expect(user).toBeDefined();
expect(user.name).toBe('Test');
});
});
通过依赖注入系统,我们可以轻松地用模拟对象替换真实依赖,使测试更加可靠和高效。
总结
服务与依赖注入是Angular架构的核心支柱,它们促进了代码的模块化、复用性和可测试性。通过@Injectable装饰器创建服务,利用providedIn配置服务范围,以及掌握组件级和应用级依赖的使用场景,是构建Angular应用的基础技能。
Angular v20对DI系统的优化,特别是树摇支持和依赖追踪机制的改进,使应用更加轻量高效。独立组件与inject()函数的结合,进一步简化了依赖管理,提高了开发效率。
在实际开发中,应遵循"单一职责"原则设计服务,合理划分服务范围,并充分利用v20的DI特性优化应用性能和可维护性。

浙公网安备 33010602011771号