跳到主要内容

Angular服务与依赖注入

Angular服务是一个广泛的类别,包含应用中用于特定目的的任何值、函数或特性。服务是一个类,它可以在应用的各个部分之间共享,通过依赖注入系统提供给需要它的组件。

核心价值

Angular服务的优势

  • 🔄 代码复用:避免重复逻辑,集中管理共享功能
  • 🧩 关注点分离:组件专注于视图,服务专注于数据和业务逻辑
  • 💉 依赖注入:松耦合架构,便于测试和维护
  • 📦 单例模式:默认单例,便于状态共享和管理
  • 🔌 可测试性:便于单元测试,支持模拟依赖

1. 服务基础

1.1 创建服务

使用Angular CLI创建服务:

bash
1ng generate service services/data
2# 或简写形式
3ng g s services/data

一个基本的服务示例:

data.service.ts
typescript
1import { Injectable } from '@angular/core';
2
3@Injectable({
4 providedIn: 'root' // 应用级单例
5})
6export class DataService {
7 private data: any[] = [];
8
9 constructor() { }
10
11 getData(): any[] {
12 return this.data;
13 }
14
15 addData(item: any): void {
16 this.data.push(item);
17 }
18
19 clearData(): void {
20 this.data = [];
21 }
22}

1.2 依赖注入

Angular的依赖注入(DI)系统是一种设计模式,它允许类接收它们的依赖,而不是自己创建它们。

注入服务

users.component.ts
typescript
1import { Component, OnInit } from '@angular/core';
2import { DataService } from '../services/data.service';
3import { LoggingService } from '../services/logging.service';
4
5@Component({
6 selector: 'app-users',
7 template: `
8 <div *ngFor="let user of users">
9 {{ user.name }}
10 </div>
11 <button (click)="addUser()">添加用户</button>
12 `
13})
14export class UsersComponent implements OnInit {
15 users: any[] = [];
16
17 constructor(
18 private dataService: DataService,
19 private logger: LoggingService
20 ) {}
21
22 ngOnInit(): void {
23 this.users = this.dataService.getData();
24 this.logger.log('已加载用户数据');
25 }
26
27 addUser(): void {
28 const newUser = { id: Date.now(), name: `User ${this.users.length + 1}` };
29 this.dataService.addData(newUser);
30 this.logger.log(`已添加用户: ${newUser.name}`);
31 }
32}

1.3 提供者和注入层级

Angular的依赖注入系统有多个级别的提供者,按从高到低的优先级排列:

  1. 元素注入器:在组件或指令上指定的提供者
  2. 模块注入器:在NgModule的providers数组中指定的提供者
  3. 根注入器:在应用的根模块或使用providedIn: 'root'指定的提供者

提供者方式

服务提供方式示例
typescript
1// 1. 在服务装饰器中提供(Angular 6+推荐方式)
2@Injectable({
3 providedIn: 'root' // 在根注入器中提供
4})
5export class GlobalService { }
6
7// 2. 在模块中提供
8@NgModule({
9 providers: [ModuleService]
10})
11export class FeatureModule { }
12
13// 3. 在组件中提供
14@Component({
15 providers: [LocalService]
16})
17export class SomeComponent { }
18
19// 4. 使用工厂函数提供
20@NgModule({
21 providers: [
22 {
23 provide: ConfigService,
24 useFactory: () => {
25 const config = new ConfigService();
26 config.apiUrl = environment.apiUrl;
27 return config;
28 }
29 }
30 ]
31})
32export class AppModule { }

1.4 提供者令牌和别名

Angular的DI系统支持使用令牌和别名来更灵活地配置依赖:

服务令牌和别名示例
typescript
1// 使用InjectionToken
2import { InjectionToken } from '@angular/core';
3
4export const API_URL = new InjectionToken<string>('api.url');
5
6@NgModule({
7 providers: [
8 { provide: API_URL, useValue: 'https://api.example.com/v1' }
9 ]
10})
11export class AppModule { }
12
13// 在组件中注入
14@Component({
15 selector: 'app-example',
16 template: `<div>API URL: {{ apiUrl }}</div>`
17})
18export class ExampleComponent {
19 constructor(@Inject(API_URL) public apiUrl: string) { }
20}
21
22// 使用类提供者别名
23@NgModule({
24 providers: [
25 { provide: BaseLogger, useClass: ProductionLogger }
26 ]
27})
28export class AppModule { }

2. 高级服务模式

2.1 单例服务与有状态服务

默认情况下,Angular服务是单例的,它们可以用于在应用的不同部分之间共享状态:

shared-state.service.ts
typescript
1import { Injectable } from '@angular/core';
2import { BehaviorSubject, Observable } from 'rxjs';
3
4export interface AppState {
5 user: any;
6 theme: string;
7 notifications: any[];
8}
9
10@Injectable({
11 providedIn: 'root'
12})
13export class SharedStateService {
14 // 初始状态
15 private initialState: AppState = {
16 user: null,
17 theme: 'light',
18 notifications: []
19 };
20
21 // 使用BehaviorSubject存储状态
22 private state$ = new BehaviorSubject<AppState>(this.initialState);
23
24 // 公开的可观察状态
25 currentState$ = this.state$.asObservable();
26
27 constructor() { }
28
29 // 获取当前状态快照
30 get currentState(): AppState {
31 return this.state$.getValue();
32 }
33
34 // 更新用户
35 updateUser(user: any): void {
36 this.state$.next({
37 ...this.currentState,
38 user
39 });
40 }
41
42 // 切换主题
43 toggleTheme(): void {
44 const newTheme = this.currentState.theme === 'light' ? 'dark' : 'light';
45 this.state$.next({
46 ...this.currentState,
47 theme: newTheme
48 });
49 }
50
51 // 添加通知
52 addNotification(notification: any): void {
53 this.state$.next({
54 ...this.currentState,
55 notifications: [...this.currentState.notifications, notification]
56 });
57 }
58
59 // 清除通知
60 clearNotifications(): void {
61 this.state$.next({
62 ...this.currentState,
63 notifications: []
64 });
65 }
66}

2.2 多实例服务

在某些情况下,您可能需要服务的多个实例,而不是单例:

counter.service.ts
typescript
1import { Injectable } from '@angular/core';
2
3// 不指定providedIn,以便在需要的地方提供它
4@Injectable()
5export class CounterService {
6 private count = 0;
7
8 increment(): void {
9 this.count++;
10 }
11
12 decrement(): void {
13 this.count--;
14 }
15
16 getCount(): number {
17 return this.count;
18 }
19
20 reset(): void {
21 this.count = 0;
22 }
23}
counter-container.component.ts
typescript
1import { Component, OnInit } from '@angular/core';
2import { CounterService } from './counter.service';
3
4@Component({
5 selector: 'app-counter-container',
6 template: `
7 <h2>Counter Containers</h2>
8
9 <app-counter title="Counter 1"></app-counter>
10
11 <app-counter title="Counter 2"></app-counter>
12 `,
13 // 每个CounterContainer将有自己的CounterService实例
14 providers: [CounterService]
15})
16export class CounterContainerComponent implements OnInit {
17 constructor() { }
18
19 ngOnInit(): void { }
20}
counter.component.ts
typescript
1import { Component, Input } from '@angular/core';
2import { CounterService } from './counter.service';
3
4@Component({
5 selector: 'app-counter',
6 template: `
7 <div class="counter">
8 <h3>{{ title }}</h3>
9 <p>Count: {{ counterService.getCount() }}</p>
10 <button (click)="counterService.increment()">+</button>
11 <button (click)="counterService.decrement()">-</button>
12 <button (click)="counterService.reset()">Reset</button>
13 </div>
14 `,
15 // 不在这里提供CounterService,
16 // 而是使用从父组件继承的实例
17})
18export class CounterComponent {
19 @Input() title: string;
20
21 constructor(public counterService: CounterService) { }
22}

3. HTTP服务与数据访问

Angular应用通常需要与后端API进行通信,HttpClient是Angular提供的用于处理HTTP请求的服务。

3.1 基础HTTP服务

首先,需要在应用模块中导入HttpClientModule:

app.module.ts
typescript
1import { NgModule } from '@angular/core';
2import { BrowserModule } from '@angular/platform-browser';
3import { HttpClientModule } from '@angular/common/http';
4
5import { AppComponent } from './app.component';
6
7@NgModule({
8 declarations: [AppComponent],
9 imports: [
10 BrowserModule,
11 HttpClientModule // 导入HTTP客户端模块
12 ],
13 providers: [],
14 bootstrap: [AppComponent]
15})
16export class AppModule { }

然后创建一个数据服务:

api.service.ts
typescript
1import { Injectable } from '@angular/core';
2import { HttpClient, HttpParams, HttpHeaders } from '@angular/common/http';
3import { Observable } from 'rxjs';
4import { catchError, retry } from 'rxjs/operators';
5
6@Injectable({
7 providedIn: 'root'
8})
9export class ApiService {
10 private apiUrl = 'https://api.example.com';
11
12 constructor(private http: HttpClient) { }
13
14 // 获取数据
15 getData<T>(endpoint: string, options?: any): Observable<T> {
16 return this.http.get<T>(`${this.apiUrl}/${endpoint}`, options);
17 }
18
19 // 提交数据
20 postData<T>(endpoint: string, data: any, options?: any): Observable<T> {
21 return this.http.post<T>(`${this.apiUrl}/${endpoint}`, data, options);
22 }
23
24 // 更新数据
25 updateData<T>(endpoint: string, data: any, options?: any): Observable<T> {
26 return this.http.put<T>(`${this.apiUrl}/${endpoint}`, data, options);
27 }
28
29 // 删除数据
30 deleteData<T>(endpoint: string, options?: any): Observable<T> {
31 return this.http.delete<T>(`${this.apiUrl}/${endpoint}`, options);
32 }
33}

3.2 处理请求选项

HttpClient支持多种请求选项,如头信息、查询参数、响应类型等:

product.service.ts
typescript
1import { Injectable } from '@angular/core';
2import { HttpClient, HttpHeaders, HttpParams, HttpErrorResponse } from '@angular/common/http';
3import { Observable, throwError } from 'rxjs';
4import { catchError, retry, tap } from 'rxjs/operators';
5import { Product } from '../models/product';
6
7@Injectable({
8 providedIn: 'root'
9})
10export class ProductService {
11 private apiUrl = 'https://api.example.com/products';
12
13 constructor(private http: HttpClient) { }
14
15 // 获取产品列表,支持分页和筛选
16 getProducts(page: number = 1, limit: number = 20, category?: string): Observable<Product[]> {
17 let params = new HttpParams()
18 .set('page', page.toString())
19 .set('limit', limit.toString());
20
21 if (category) {
22 params = params.set('category', category);
23 }
24
25 return this.http.get<Product[]>(this.apiUrl, { params })
26 .pipe(
27 retry(2), // 失败时重试2次
28 catchError(this.handleError)
29 );
30 }
31
32 // 获取单个产品
33 getProduct(id: string): Observable<Product> {
34 return this.http.get<Product>(`${this.apiUrl}/${id}`)
35 .pipe(catchError(this.handleError));
36 }
37
38 // 创建产品
39 createProduct(product: Product): Observable<Product> {
40 const headers = new HttpHeaders({
41 'Content-Type': 'application/json',
42 'Authorization': `Bearer ${this.getAuthToken()}`
43 });
44
45 return this.http.post<Product>(this.apiUrl, product, { headers })
46 .pipe(
47 tap(newProduct => console.log(`Created product with ID: ${newProduct.id}`)),
48 catchError(this.handleError)
49 );
50 }
51
52 // 更新产品
53 updateProduct(product: Product): Observable<Product> {
54 const headers = new HttpHeaders({
55 'Content-Type': 'application/json',
56 'Authorization': `Bearer ${this.getAuthToken()}`
57 });
58
59 return this.http.put<Product>(`${this.apiUrl}/${product.id}`, product, { headers })
60 .pipe(catchError(this.handleError));
61 }
62
63 // 删除产品
64 deleteProduct(id: string): Observable<any> {
65 const headers = new HttpHeaders({
66 'Authorization': `Bearer ${this.getAuthToken()}`
67 });
68
69 return this.http.delete(`${this.apiUrl}/${id}`, { headers })
70 .pipe(catchError(this.handleError));
71 }
72
73 // 获取认证令牌(示例)
74 private getAuthToken(): string {
75 return localStorage.getItem('auth_token') || '';
76 }
77
78 // 错误处理
79 private handleError(error: HttpErrorResponse) {
80 let errorMessage = '';
81
82 if (error.error instanceof ErrorEvent) {
83 // 客户端错误
84 errorMessage = `客户端错误: ${error.error.message}`;
85 } else {
86 // 服务器错误
87 errorMessage = `服务器错误代码: ${error.status}, 消息: ${error.message}`;
88 }
89
90 console.error(errorMessage);
91 return throwError(() => new Error(errorMessage));
92 }
93}

3.3 HTTP拦截器

HTTP拦截器允许您在请求离开应用或响应返回应用之前拦截并修改它们:

auth.interceptor.ts
typescript
1import { Injectable } from '@angular/core';
2import {
3 HttpRequest,
4 HttpHandler,
5 HttpEvent,
6 HttpInterceptor,
7 HttpErrorResponse
8} from '@angular/common/http';
9import { Observable, throwError } from 'rxjs';
10import { catchError, switchMap } from 'rxjs/operators';
11import { AuthService } from './auth.service';
12
13@Injectable()
14export class AuthInterceptor implements HttpInterceptor {
15 constructor(private authService: AuthService) {}
16
17 intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
18 // 如果需要认证的请求,添加令牌
19 if (this.authService.isLoggedIn()) {
20 request = this.addToken(request, this.authService.getAccessToken());
21 }
22
23 // 处理响应
24 return next.handle(request).pipe(
25 catchError((error: HttpErrorResponse) => {
26 // 如果401未授权错误且有刷新令牌,尝试刷新令牌
27 if (error.status === 401 && this.authService.hasRefreshToken()) {
28 return this.handle401Error(request, next);
29 }
30
31 return throwError(() => error);
32 })
33 );
34 }
35
36 // 添加认证令牌到请求头
37 private addToken(request: HttpRequest<any>, token: string): HttpRequest<any> {
38 return request.clone({
39 setHeaders: {
40 Authorization: `Bearer ${token}`
41 }
42 });
43 }
44
45 // 处理401错误 - 尝试刷新令牌
46 private handle401Error(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
47 return this.authService.refreshToken().pipe(
48 switchMap(token => {
49 return next.handle(this.addToken(request, token));
50 }),
51 catchError(error => {
52 // 刷新令牌也失败,登出用户
53 this.authService.logout();
54 return throwError(() => error);
55 })
56 );
57 }
58}

在应用模块中注册拦截器:

app.module.ts
typescript
1import { NgModule } from '@angular/core';
2import { BrowserModule } from '@angular/platform-browser';
3import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http';
4import { AuthInterceptor } from './services/auth.interceptor';
5
6@NgModule({
7 declarations: [AppComponent],
8 imports: [
9 BrowserModule,
10 HttpClientModule
11 ],
12 providers: [
13 { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }
14 ],
15 bootstrap: [AppComponent]
16})
17export class AppModule { }

4. 缓存策略与性能优化

4.1 服务缓存模式

在与后端API通信时,实现缓存可以提高应用性能,减少网络请求:

cached-product.service.ts
typescript
1import { Injectable } from '@angular/core';
2import { HttpClient } from '@angular/common/http';
3import { Observable, of, throwError } from 'rxjs';
4import { tap, catchError, shareReplay } from 'rxjs/operators';
5import { Product } from '../models/product';
6
7@Injectable({
8 providedIn: 'root'
9})
10export class CachedProductService {
11 private apiUrl = 'https://api.example.com/products';
12
13 // 缓存
14 private productsCache: { [key: string]: Observable<Product[]> } = {};
15 private productCache: { [id: string]: Observable<Product> } = {};
16
17 // 缓存过期时间(毫秒)
18 private cacheExpiry = 5 * 60 * 1000; // 5分钟
19 private cacheTimestamps: { [key: string]: number } = {};
20
21 constructor(private http: HttpClient) { }
22
23 getProducts(category?: string): Observable<Product[]> {
24 const cacheKey = category || 'all';
25
26 // 检查缓存是否过期
27 if (this.isCacheValid(cacheKey)) {
28 console.log(`Using cached products for category: ${cacheKey}`);
29 return this.productsCache[cacheKey];
30 }
31
32 // 无缓存或已过期,发送新请求
33 const url = category ? `${this.apiUrl}?category=${category}` : this.apiUrl;
34
35 // 使用shareReplay创建多播Observable,缓存最新结果
36 this.productsCache[cacheKey] = this.http.get<Product[]>(url).pipe(
37 tap(() => this.setCacheTimestamp(cacheKey)),
38 shareReplay(1),
39 catchError(error => {
40 delete this.productsCache[cacheKey]; // 移除失败的缓存
41 return throwError(() => error);
42 })
43 );
44
45 return this.productsCache[cacheKey];
46 }
47
48 getProduct(id: string): Observable<Product> {
49 // 检查缓存是否过期
50 if (this.isCacheValid(`product_${id}`)) {
51 console.log(`Using cached product: ${id}`);
52 return this.productCache[id];
53 }
54
55 // 无缓存或已过期,发送新请求
56 this.productCache[id] = this.http.get<Product>(`${this.apiUrl}/${id}`).pipe(
57 tap(() => this.setCacheTimestamp(`product_${id}`)),
58 shareReplay(1),
59 catchError(error => {
60 delete this.productCache[id]; // 移除失败的缓存
61 return throwError(() => error);
62 })
63 );
64
65 return this.productCache[id];
66 }
67
68 // 清除特定产品缓存
69 clearProductCache(id: string): void {
70 if (this.productCache[id]) {
71 delete this.productCache[id];
72 delete this.cacheTimestamps[`product_${id}`];
73 }
74 }
75
76 // 清除所有缓存
77 clearCache(): void {
78 this.productsCache = {};
79 this.productCache = {};
80 this.cacheTimestamps = {};
81 }
82
83 // 检查缓存是否有效
84 private isCacheValid(key: string): boolean {
85 if (!this.productsCache[key] && !this.productCache[key]) {
86 return false; // 没有缓存
87 }
88
89 const timestamp = this.cacheTimestamps[key];
90 if (!timestamp) return false;
91
92 return (Date.now() - timestamp) < this.cacheExpiry;
93 }
94
95 // 设置缓存时间戳
96 private setCacheTimestamp(key: string): void {
97 this.cacheTimestamps[key] = Date.now();
98 }
99}

4.2 服务测试

Angular服务是非常适合测试的单元,因为它们不依赖DOM或组件生命周期:

product.service.spec.ts
typescript
1import { TestBed } from '@angular/core/testing';
2import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
3import { ProductService } from './product.service';
4import { Product } from '../models/product';
5
6describe('ProductService', () => {
7 let service: ProductService;
8 let httpMock: HttpTestingController;
9
10 beforeEach(() => {
11 TestBed.configureTestingModule({
12 imports: [HttpClientTestingModule],
13 providers: [ProductService]
14 });
15
16 service = TestBed.inject(ProductService);
17 httpMock = TestBed.inject(HttpTestingController);
18 });
19
20 afterEach(() => {
21 // 验证没有未处理的请求
22 httpMock.verify();
23 });
24
25 it('should be created', () => {
26 expect(service).toBeTruthy();
27 });
28
29 describe('getProducts', () => {
30 it('should return products', () => {
31 const mockProducts: Product[] = [
32 { id: '1', name: 'Product 1', price: 100 },
33 { id: '2', name: 'Product 2', price: 200 }
34 ];
35
36 // 调用服务方法
37 service.getProducts().subscribe(products => {
38 expect(products.length).toBe(2);
39 expect(products).toEqual(mockProducts);
40 });
41
42 // 模拟HTTP请求
43 const req = httpMock.expectOne('https://api.example.com/products');
44 expect(req.request.method).toBe('GET');
45
46 // 解析请求
47 req.flush(mockProducts);
48 });
49
50 it('should include category in query params when provided', () => {
51 // 调用服务方法,传递category参数
52 service.getProducts(1, 10, 'electronics').subscribe();
53
54 // 模拟并验证HTTP请求
55 const req = httpMock.expectOne(req => {
56 return req.url === 'https://api.example.com/products' &&
57 req.params.get('category') === 'electronics' &&
58 req.params.get('page') === '1' &&
59 req.params.get('limit') === '10';
60 });
61
62 expect(req.request.method).toBe('GET');
63 req.flush([]);
64 });
65
66 it('should handle error', () => {
67 // 调用服务方法
68 service.getProducts().subscribe({
69 next: () => fail('应该失败'),
70 error: error => {
71 expect(error).toBeTruthy();
72 expect(error.message).toContain('服务器错误代码: 500');
73 }
74 });
75
76 // 模拟HTTP请求并返回错误
77 const req = httpMock.expectOne('https://api.example.com/products');
78 req.flush('Server error', { status: 500, statusText: 'Internal Server Error' });
79 });
80 });
81
82 describe('getProduct', () => {
83 it('should return a single product', () => {
84 const mockProduct: Product = { id: '1', name: 'Product 1', price: 100 };
85
86 // 调用服务方法
87 service.getProduct('1').subscribe(product => {
88 expect(product).toEqual(mockProduct);
89 });
90
91 // 模拟HTTP请求
92 const req = httpMock.expectOne('https://api.example.com/products/1');
93 expect(req.request.method).toBe('GET');
94
95 // 解析请求
96 req.flush(mockProduct);
97 });
98 });
99});

测试带有依赖的服务:

auth.service.spec.ts
typescript
1import { TestBed } from '@angular/core/testing';
2import { HttpClientTestingModule } from '@angular/common/http/testing';
3import { AuthService } from './auth.service';
4import { StorageService } from './storage.service';
5
6// 创建模拟服务
7class MockStorageService {
8 private data: { [key: string]: string } = {};
9
10 getItem(key: string): string {
11 return this.data[key] || null;
12 }
13
14 setItem(key: string, value: string): void {
15 this.data[key] = value;
16 }
17
18 removeItem(key: string): void {
19 delete this.data[key];
20 }
21}
22
23describe('AuthService', () => {
24 let service: AuthService;
25 let storageService: StorageService;
26
27 beforeEach(() => {
28 TestBed.configureTestingModule({
29 imports: [HttpClientTestingModule],
30 providers: [
31 AuthService,
32 { provide: StorageService, useClass: MockStorageService }
33 ]
34 });
35
36 service = TestBed.inject(AuthService);
37 storageService = TestBed.inject(StorageService);
38 });
39
40 it('should be created', () => {
41 expect(service).toBeTruthy();
42 });
43
44 it('should return isLoggedIn as false when no token', () => {
45 // 使用jasmine spy模拟storageService.getItem方法
46 spyOn(storageService, 'getItem').and.returnValue(null);
47
48 expect(service.isLoggedIn()).toBeFalse();
49 expect(storageService.getItem).toHaveBeenCalledWith('auth_token');
50 });
51
52 it('should return isLoggedIn as true when token exists', () => {
53 spyOn(storageService, 'getItem').and.returnValue('fake-token');
54
55 expect(service.isLoggedIn()).toBeTrue();
56 expect(storageService.getItem).toHaveBeenCalledWith('auth_token');
57 });
58});

5. 服务最佳实践

5.1 服务设计原则

设计高质量的Angular服务应遵循以下原则:

  1. 单一责任:每个服务应该有一个明确的责任
  2. 封装:隐藏内部实现细节,只暴露必要的公共API
  3. 可测试性:服务应该易于单元测试
  4. 松耦合:服务之间应该尽量减少依赖
  5. 不可变性:尽量使用不可变数据结构,避免副作用

5.2 服务组织模式

核心服务与特性服务

将服务分为两类有助于更好地组织代码:

  1. 核心服务:应用级别的单例服务,如认证、日志记录、HTTP通信
  2. 特性服务:与特定功能模块关联的服务,如用户管理、产品管理
core/core.module.ts
typescript
1import { NgModule, Optional, SkipSelf } from '@angular/core';
2import { CommonModule } from '@angular/common';
3import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
4
5import { AuthService } from './services/auth.service';
6import { LoggingService } from './services/logging.service';
7import { ApiService } from './services/api.service';
8import { AuthInterceptor } from './interceptors/auth.interceptor';
9import { LoggingInterceptor } from './interceptors/logging.interceptor';
10
11@NgModule({
12 imports: [
13 CommonModule,
14 HttpClientModule
15 ],
16 providers: [
17 AuthService,
18 LoggingService,
19 ApiService,
20 { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },
21 { provide: HTTP_INTERCEPTORS, useClass: LoggingInterceptor, multi: true }
22 ]
23})
24export class CoreModule {
25 // 防止CoreModule被多次导入
26 constructor(@Optional() @SkipSelf() parentModule: CoreModule) {
27 if (parentModule) {
28 throw new Error('CoreModule已经加载。应该只在AppModule中导入CoreModule。');
29 }
30 }
31}

服务继承与组合

选择合适的服务设计模式:

继承示例
typescript
1// 基础服务
2@Injectable()
3export class BaseApiService<T> {
4 constructor(protected http: HttpClient) {}
5
6 getAll(url: string): Observable<T[]> {
7 return this.http.get<T[]>(url);
8 }
9
10 getById(url: string, id: string): Observable<T> {
11 return this.http.get<T>(`${url}/${id}`);
12 }
13
14 create(url: string, entity: T): Observable<T> {
15 return this.http.post<T>(url, entity);
16 }
17
18 update(url: string, id: string, entity: T): Observable<T> {
19 return this.http.put<T>(`${url}/${id}`, entity);
20 }
21
22 delete(url: string, id: string): Observable<void> {
23 return this.http.delete<void>(`${url}/${id}`);
24 }
25}
26
27// 派生服务
28@Injectable({
29 providedIn: 'root'
30})
31export class UserService extends BaseApiService<User> {
32 private apiUrl = 'https://api.example.com/users';
33
34 constructor(http: HttpClient) {
35 super(http);
36 }
37
38 getAllUsers(): Observable<User[]> {
39 return this.getAll(this.apiUrl);
40 }
41
42 getUserById(id: string): Observable<User> {
43 return this.getById(this.apiUrl, id);
44 }
45
46 // 特定于用户的方法
47 getUsersByDepartment(department: string): Observable<User[]> {
48 return this.http.get<User[]>(`${this.apiUrl}/department/${department}`);
49 }
50}
组合示例
typescript
1@Injectable({
2 providedIn: 'root'
3})
4export class ProductService {
5 private apiUrl = 'https://api.example.com/products';
6
7 constructor(
8 private api: ApiService,
9 private logger: LoggingService,
10 private analytics: AnalyticsService
11 ) {}
12
13 getProducts(): Observable<Product[]> {
14 this.logger.log('Fetching products');
15 return this.api.getData<Product[]>(this.apiUrl).pipe(
16 tap(products => {
17 this.analytics.trackEvent('Products_Loaded', { count: products.length });
18 })
19 );
20 }
21}

5.3 性能与优化

提高服务性能的技巧:

  1. 使用Observable操作符shareReplaydistinctUntilChangeddebounceTime
  2. 实现缓存策略:缓存频繁使用的数据
  3. 批量请求:使用forkJoin合并多个请求
  4. 取消请求:在不需要时取消HTTP请求
  5. 懒加载服务:使用延迟加载特性模块延迟加载服务
优化示例
typescript
1@Injectable({
2 providedIn: 'root'
3})
4export class OptimizedDataService {
5 // 使用ReplaySubject缓存最新值
6 private _data$ = new ReplaySubject<any[]>(1);
7
8 // 公开为只读Observable
9 readonly data$ = this._data$.asObservable();
10
11 // 跟踪加载状态
12 private loading = false;
13
14 constructor(private http: HttpClient) {}
15
16 // 只在需要时加载数据
17 loadData(): Observable<any[]> {
18 if (!this.loading) {
19 this.loading = true;
20
21 this.http.get<any[]>('api/data').pipe(
22 // 确保数据只被获取一次,即使有多个订阅者
23 shareReplay(1),
24 // 无论成功还是失败,都重置loading状态
25 finalize(() => this.loading = false)
26 ).subscribe(data => {
27 this._data$.next(data);
28 });
29 }
30
31 return this.data$;
32 }
33
34 // 批量请求示例
35 batchRequests(ids: string[]): Observable<any[]> {
36 // 创建多个请求的数组
37 const requests = ids.map(id =>
38 this.http.get<any>(`api/data/${id}`)
39 );
40
41 // 并行执行所有请求,等待所有请求完成
42 return forkJoin(requests);
43 }
44
45 // 可取消的请求
46 searchWithCancel(term: string, cancelPrevious: Subject<void>): Observable<any[]> {
47 // 取消之前的请求
48 cancelPrevious.next();
49
50 return this.http.get<any[]>(`api/search?q=${term}`).pipe(
51 takeUntil(cancelPrevious)
52 );
53 }
54}

评论