Angular服务与依赖注入
Angular服务是一个广泛的类别,包含应用中用于特定目的的任何值、函数或特性。服务是一个类,它可以在应用的各个部分之间共享,通过依赖注入系统提供给需要它的组件。
核心价值
Angular服务的优势
- 🔄 代码复用:避免重复逻辑,集中管理共享功能
- 🧩 关注点分离:组件专注于视图,服务专注于数据和业务逻辑
- 💉 依赖注入:松耦合架构,便于测试和维护
- 📦 单例模式:默认单例,便于状态共享和管理
- 🔌 可测试性:便于单元测试,支持模拟依赖
1. 服务基础
1.1 创建服务
使用Angular CLI创建服务:
bash
1ng generate service services/data2# 或简写形式3ng g s services/data一个基本的服务示例:
data.service.ts
typescript
1import { Injectable } from '@angular/core';23@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';45@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: LoggingService20 ) {}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的依赖注入系统有多个级别的提供者,按从高到低的优先级排列:
- 元素注入器:在组件或指令上指定的提供者
- 模块注入器:在NgModule的providers数组中指定的提供者
- 根注入器:在应用的根模块或使用
providedIn: 'root'指定的提供者
提供者方式
服务提供方式示例
typescript
1// 1. 在服务装饰器中提供(Angular 6+推荐方式)2@Injectable({3 providedIn: 'root' // 在根注入器中提供4})5export class GlobalService { }67// 2. 在模块中提供8@NgModule({9 providers: [ModuleService]10})11export class FeatureModule { }1213// 3. 在组件中提供14@Component({15 providers: [LocalService]16})17export class SomeComponent { }1819// 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// 使用InjectionToken2import { InjectionToken } from '@angular/core';34export const API_URL = new InjectionToken<string>('api.url');56@NgModule({7 providers: [8 { provide: API_URL, useValue: 'https://api.example.com/v1' }9 ]10})11export class AppModule { }1213// 在组件中注入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}2122// 使用类提供者别名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';34export interface AppState {5 user: any;6 theme: string;7 notifications: any[];8}910@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 user39 });40 }41 42 // 切换主题43 toggleTheme(): void {44 const newTheme = this.currentState.theme === 'light' ? 'dark' : 'light';45 this.state$.next({46 ...this.currentState,47 theme: newTheme48 });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';23// 不指定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';34@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';34@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';45import { AppComponent } from './app.component';67@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';56@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';67@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 HttpErrorResponse8} from '@angular/common/http';9import { Observable, throwError } from 'rxjs';10import { catchError, switchMap } from 'rxjs/operators';11import { AuthService } from './auth.service';1213@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';56@NgModule({7 declarations: [AppComponent],8 imports: [9 BrowserModule,10 HttpClientModule11 ],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';67@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';56describe('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';56// 创建模拟服务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}2223describe('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服务应遵循以下原则:
- 单一责任:每个服务应该有一个明确的责任
- 封装:隐藏内部实现细节,只暴露必要的公共API
- 可测试性:服务应该易于单元测试
- 松耦合:服务之间应该尽量减少依赖
- 不可变性:尽量使用不可变数据结构,避免副作用
5.2 服务组织模式
核心服务与特性服务
将服务分为两类有助于更好地组织代码:
- 核心服务:应用级别的单例服务,如认证、日志记录、HTTP通信
- 特性服务:与特定功能模块关联的服务,如用户管理、产品管理
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';45import { 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';1011@NgModule({12 imports: [13 CommonModule,14 HttpClientModule15 ],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}2627// 派生服务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: AnalyticsService11 ) {}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 性能与优化
提高服务性能的技巧:
- 使用Observable操作符:
shareReplay、distinctUntilChanged、debounceTime等 - 实现缓存策略:缓存频繁使用的数据
- 批量请求:使用
forkJoin合并多个请求 - 取消请求:在不需要时取消HTTP请求
- 懒加载服务:使用延迟加载特性模块延迟加载服务
优化示例
typescript
1@Injectable({2 providedIn: 'root'3})4export class OptimizedDataService {5 // 使用ReplaySubject缓存最新值6 private _data$ = new ReplaySubject<any[]>(1);7 8 // 公开为只读Observable9 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}
参与讨论