Angular服务与依赖注入

本章目标:深入理解Angular服务和依赖注入机制,掌握服务创建、提供、注入的全过程,学会使用服务实现组件通信和数据共享。

服务与依赖注入核心概念

服务

封装业务逻辑和数据访问

组件

使用服务的消费者

依赖注入

连接服务与组件的桥梁

什么是Angular服务?

服务是Angular中用于封装可重用业务逻辑、数据访问、工具函数等的类。服务的主要特点:

业务逻辑封装

将复杂的业务逻辑从组件中分离出来,使组件更专注于视图展示。

  • 数据处理和转换
  • 业务规则验证
  • 计算和算法
  • 状态管理
数据访问层

封装所有与后端API的交互,提供统一的数据访问接口。

  • HTTP请求处理
  • WebSocket通信
  • 本地存储操作
  • 数据缓存管理
工具和辅助函数

提供通用的工具函数和辅助方法,可在整个应用中复用。

  • 日期格式化
  • 字符串处理
  • 数学计算
  • 验证函数

什么是依赖注入?

依赖注入(Dependency Injection,DI)是Angular的核心特性,它是一种设计模式,用于管理对象之间的依赖关系。

依赖注入工作流程
1. 声明依赖

组件/服务在构造函数中声明所需的服务

2. 查找提供者

Angular在注入器树中查找服务的提供者

3. 创建实例

如果找到提供者,创建服务实例

4. 注入依赖

将服务实例注入到请求者中

1. 创建服务

使用Angular CLI可以快速创建服务,也可以手动创建。

# 使用CLI创建服务
ng generate service data
# 或简写
ng g s data

# 创建带路径的服务
ng g s core/services/auth

# 创建服务并自动注册到根模块
ng g s logging --providedIn root

手动创建服务

服务是一个普通的TypeScript类,使用@Injectable()装饰器标记:

// data.service.ts
import { Injectable } from '@angular/core';

// 使用@Injectable装饰器
@Injectable({
  providedIn: 'root' // 在根注入器中提供此服务
})
export class DataService {
  private data: any[] = [];

  constructor() {
    console.log('DataService 实例已创建');
  }

  // 添加数据
  addData(item: any): void {
    this.data.push(item);
    console.log('数据已添加:', item);
  }

  // 获取所有数据
  getAllData(): any[] {
    return [...this.data]; // 返回副本,保护原始数据
  }

  // 获取特定数据
  getDataById(id: number): any {
    return this.data.find(item => item.id === id);
  }

  // 更新数据
  updateData(id: number, newData: any): boolean {
    const index = this.data.findIndex(item => item.id === id);
    if (index !== -1) {
      this.data[index] = { ...this.data[index], ...newData };
      return true;
    }
    return false;
  }

  // 删除数据
  deleteData(id: number): boolean {
    const initialLength = this.data.length;
    this.data = this.data.filter(item => item.id !== id);
    return this.data.length < initialLength;
  }

  // 数据统计
  getDataCount(): number {
    return this.data.length;
  }

  // 清空数据
  clearData(): void {
    this.data = [];
  }
}

@Injectable装饰器详解

元数据属性 描述 示例
providedIn 指定服务在哪个注入器中可用 providedIn: 'root'
scope 指定服务的生命周期范围(Angular 14+) scope: 'platform'

2. 依赖注入机制

Angular使用分层注入器系统来管理依赖注入。

注入器层级结构
平台注入器 (PlatformInjector)

最高级别的注入器,在整个平台中共享的单例服务

providedIn: 'platform'
根注入器 (RootInjector)

应用级别的单例服务,在整个应用中共享

providedIn: 'root'
模块注入器 (NgModule Injector)

模块级别的服务,在模块范围内共享

providers: [MyService]
组件注入器 (Component Injector)

组件级别的服务,每个组件实例都有独立的服务实例

providers: [MyService] 在组件元数据中

注入服务的三种方式

// 1. 通过构造函数注入(最常用)
import { Component } from '@angular/core';
import { DataService } from './data.service';

@Component({
  selector: 'app-user-list',
  template: `...`
})
export class UserListComponent {
  // 在构造函数中声明依赖
  constructor(private dataService: DataService) {
    // 现在可以在组件中使用dataService
  }

  ngOnInit(): void {
    const data = this.dataService.getAllData();
    console.log('获取到的数据:', data);
  }
}

// 2. 使用@Inject装饰器注入
import { Component, Inject } from '@angular/core';
import { DataService } from './data.service';

@Component({...})
export class AnotherComponent {
  constructor(@Inject(DataService) private dataService: DataService) {}
}

// 3. 注入可选依赖(服务可能不存在)
import { Component, Optional } from '@angular/core';
import { OptionalService } from './optional.service';

@Component({...})
export class OptionalComponent {
  constructor(@Optional() private optionalService?: OptionalService) {
    if (this.optionalService) {
      // 服务存在时的逻辑
    } else {
      // 服务不存在时的逻辑
    }
  }
}

3. 服务提供方式

服务可以在多个级别提供,不同的提供方式会影响服务的生命周期和可见范围。

根级提供
应用级单例
@Injectable({
  providedIn: 'root'
})
export class AuthService {
  // 整个应用共享一个实例
}
  • 优点:自动清理,延迟加载
  • 缺点:所有组件共享同一实例
  • 适用场景:用户认证、全局配置
模块级提供
模块内单例
@NgModule({
  providers: [DataService]
})
export class FeatureModule {
  // 模块内所有组件共享实例
}
  • 优点:模块级别控制
  • 缺点:需要手动清理
  • 适用场景:功能模块专用服务
组件级提供
组件实例级
@Component({
  providers: [UserService]
})
export class UserComponent {
  // 每个组件实例有独立服务实例
}
  • 优点:组件隔离,独立状态
  • 缺点:内存消耗较大
  • 适用场景:组件专用状态管理

工厂提供者

使用工厂函数创建服务实例,可以添加额外的配置逻辑:

// 1. 使用工厂函数
export function loggerFactory(isProd: boolean): LoggerService {
  return isProd ?
    new ProductionLogger() :
    new DevelopmentLogger();
}

// 2. 在模块中注册
@NgModule({
  providers: [
    {
      provide: LoggerService,
      useFactory: loggerFactory,
      deps: [EnvironmentService]
    }
  ]
})

// 3. 或者使用类提供者
@NgModule({
  providers: [
    { provide: OldService, useClass: NewService }
  ]
})

// 4. 使用值提供者
@NgModule({
  providers: [
    { provide: 'API_URL', useValue: 'https://api.example.com' }
  ]
})

// 5. 在组件中注入值
constructor(@Inject('API_URL') private apiUrl: string) {}

4. 服务生命周期

服务的生命周期由提供方式决定,理解生命周期有助于管理资源和避免内存泄漏。

服务生命周期流程
1. 创建实例

当服务第一次被请求时,Angular创建服务实例

  • 执行构造函数
  • 注入依赖的服务
  • 初始化内部状态
2. 使用阶段

服务在整个生命周期中提供服务

  • 组件调用服务方法
  • 服务处理业务逻辑
  • 服务状态可能变化
3. 销毁阶段

当注入器销毁时,服务实例被销毁

  • 执行清理逻辑(如果有)
  • 取消订阅(重要!)
  • 释放资源

实现OnDestroy接口

对于需要清理资源的服务,应该实现OnDestroy接口:

import { Injectable, OnDestroy } from '@angular/core';
import { Subscription, interval } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class TimerService implements OnDestroy {
  private timerSubscription?: Subscription;
  private seconds = 0;

  startTimer(): void {
    // 清理现有的订阅
    this.stopTimer();

    // 创建新的定时器
    this.timerSubscription = interval(1000).subscribe(() => {
      this.seconds++;
      console.log(`计时: ${this.seconds}秒`);
    });
  }

  stopTimer(): void {
    if (this.timerSubscription) {
      this.timerSubscription.unsubscribe();
      this.timerSubscription = undefined;
    }
  }

  getSeconds(): number {
    return this.seconds;
  }

  // 实现OnDestroy接口
  ngOnDestroy(): void {
    console.log('TimerService 正在销毁');
    // 必须清理所有订阅!
    this.stopTimer();
  }
}

5. 使用服务实现组件通信

服务是实现非父子组件通信的最佳方式,特别是使用RxJS的Subject或BehaviorSubject。

组件通信实时演示

消息总线服务实现

// message-bus.service.ts
import { Injectable } from '@angular/core';
import { Subject, BehaviorSubject, Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class MessageBusService {
  // 用于发送一次性消息的Subject
  private messageSubject = new Subject<any>();

  // 用于共享状态的BehaviorSubject(需要初始值)
  private counterSubject = new BehaviorSubject<number>(0);
  private themeSubject = new BehaviorSubject<string>('light');

  // 公共可观察对象(组件只订阅,不直接操作Subject)
  public messages$: Observable<any> = this.messageSubject.asObservable();
  public counter$: Observable<number> = this.counterSubject.asObservable();
  public theme$: Observable<string> = this.themeSubject.asObservable();

  // 发送消息的方法
  sendMessage(message: any): void {
    this.messageSubject.next({
      ...message,
      timestamp: new Date()
    });
  }

  // 更新计数器
  incrementCounter(): void {
    const current = this.counterSubject.value;
    this.counterSubject.next(current + 1);
  }

  decrementCounter(): void {
    const current = this.counterSubject.value;
    this.counterSubject.next(current - 1);
  }

  // 切换主题
  toggleTheme(): void {
    const currentTheme = this.themeSubject.value;
    const newTheme = currentTheme === 'light' ? 'dark' : 'light';
    this.themeSubject.next(newTheme);
  }

  // 获取当前值
  getCurrentCounter(): number {
    return this.counterSubject.value;
  }

  getCurrentTheme(): string {
    return this.themeSubject.value;
  }
}

组件中使用消息总线

// sender.component.ts - 发送消息的组件
import { Component } from '@angular/core';
import { MessageBusService } from './message-bus.service';

@Component({
  selector: 'app-sender',
  template: `
    <button (click)="sendGreeting()">
      发送问候
    </button>
  `
})
export class SenderComponent {
  constructor(private messageBus: MessageBusService) {}

  sendGreeting(): void {
    this.messageBus.sendMessage({
      type: 'greeting',
      content: 'Hello from Sender!'
    });
  }
}

// receiver.component.ts - 接收消息的组件
import { Component, OnInit, OnDestroy } from '@angular/core';
import { MessageBusService } from './message-bus.service';
import { Subscription } from 'rxjs';

@Component({
  selector: 'app-receiver',
  template: `
    <div>收到消息: {{ lastMessage }}</div>
    <div>计数: {{ counter }}</div>
    <div>主题: {{ theme }}</div>
  `
})
export class ReceiverComponent implements OnInit, OnDestroy {
  lastMessage: any = null;
  counter = 0;
  theme = 'light';

  private subscriptions: Subscription[] = [];

  constructor(private messageBus: MessageBusService) {}

  ngOnInit(): void {
    // 订阅消息
    this.subscriptions.push(
      this.messageBus.messages$.subscribe(message => {
        this.lastMessage = message;
        console.log('收到消息:', message);
      })
    );

    // 订阅计数器
    this.subscriptions.push(
      this.messageBus.counter$.subscribe(count => {
        this.counter = count;
      })
    );

    // 订阅主题
    this.subscriptions.push(
      this.messageBus.theme$.subscribe(theme => {
        this.theme = theme;
      })
    );
  }

  ngOnDestroy(): void {
    // 清理所有订阅
    this.subscriptions.forEach(sub => sub.unsubscribe());
  }
}

6. 实际应用示例

创建完整的用户认证服务,展示实际开发中的服务设计模式。

// auth.service.ts - 用户认证服务
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { BehaviorSubject, Observable, of } from 'rxjs';
import { tap, catchError } from 'rxjs/operators';
import { Router } from '@angular/router';

export interface User {
  id: number;
  username: string;
  email: string;
  token?: string;
}

export interface LoginCredentials {
  username: string;
  password: string;
}

@Injectable({
  providedIn: 'root'
})
export class AuthService {
  private readonly STORAGE_KEY = 'auth_user';
  private apiUrl = 'https://api.example.com/auth';

  // 使用BehaviorSubject存储当前用户
  private currentUserSubject = new BehaviorSubject<User | null>(null);
  public currentUser$: Observable<User | null> = this.currentUserSubject.asObservable();

  // 检查是否已登录
  get isLoggedIn(): boolean {
    return !!this.currentUserSubject.value;
  }

  // 获取当前用户(同步)
  get currentUser(): User | null {
    return this.currentUserSubject.value;
  }

  constructor(
    private http: HttpClient,
    private router: Router
  ) {
    // 从本地存储恢复登录状态
    this.loadUserFromStorage();
  }

  // 登录方法
  login(credentials: LoginCredentials): Observable<User> {
    return this.http.post<User>(`${this.apiUrl}/login`, credentials)
      .pipe(
        tap(user => {
          // 保存用户信息和token
          this.saveUser(user);
          this.currentUserSubject.next(user);
          console.log('登录成功:', user.username);
        }),
        catchError(error => {
          console.error('登录失败:', error);
          throw error;
        })
      );
  }

  // 注册方法
  register(userData: any): Observable<User> {
    return this.http.post<User>(`${this.apiUrl}/register`, userData)
      .pipe(
        tap(user => {
          this.saveUser(user);
          this.currentUserSubject.next(user);
          console.log('注册成功:', user.username);
        })
      );
  }

  // 退出登录
  logout(): void {
    // 清除本地存储
    localStorage.removeItem(this.STORAGE_KEY);
    // 更新状态
    this.currentUserSubject.next(null);
    // 跳转到登录页
    this.router.navigate(['/login']);
    console.log('已退出登录');
  }

  // 更新用户信息
  updateUser(userData: Partial<User>): Observable<User> {
    const updatedUser = { ...this.currentUser, ...userData } as User;

    return this.http.put<User>(`${this.apiUrl}/user`, updatedUser)
      .pipe(
        tap(user => {
          this.saveUser(user);
          this.currentUserSubject.next(user);
          console.log('用户信息已更新');
        })
      );
  }

  // 验证token是否有效
  validateToken(): Observable<boolean> {
    if (!this.currentUser?.token) {
      return of(false);
    }

    return this.http.post<{ valid: boolean }>(`${this.apiUrl}/validate`, {
      token: this.currentUser.token
    }).pipe(
      tap(response => {
        if (!response.valid) {
          this.logout();
        }
      }),
      catchError(() => {
        this.logout();
        return of(false);
      })
    );
  }

  // 保存用户到本地存储
  private saveUser(user: User): void {
    localStorage.setItem(this.STORAGE_KEY, JSON.stringify(user));
  }

  // 从本地存储加载用户
  private loadUserFromStorage(): void {
    try {
      const userJson = localStorage.getItem(this.STORAGE_KEY);
      if (userJson) {
        const user = JSON.parse(userJson);
        this.currentUserSubject.next(user);
      }
    } catch (error) {
      console.error('加载用户信息失败:', error);
      localStorage.removeItem(this.STORAGE_KEY);
    }
  }
}

服务最佳实践

推荐做法
  • 为服务选择描述性的、具体的名称
  • 使用providedIn: 'root'除非有特殊需求
  • 保持服务职责单一,功能聚焦
  • 在服务中处理所有业务逻辑,保持组件精简
  • 使用RxJS Subject/BehaviorSubject进行状态管理
  • ngOnDestroy中清理所有订阅
  • 为服务编写单元测试
  • 使用接口定义服务契约
应避免的做法
  • 避免在服务中直接操作DOM
  • 不要在服务构造函数中执行复杂逻辑
  • 避免创建过于庞大、功能混杂的服务
  • 不要忘记取消订阅Observable
  • 避免在多个服务之间形成循环依赖
  • 不要在服务中硬编码配置值
  • 避免在服务中直接修改组件状态
  • 不要过度使用服务,简单的组件逻辑无需服务

常见服务模式速查表

模式 用途 示例
单例服务 全局共享状态和逻辑 providedIn: 'root'
数据服务 封装API调用和数据访问 HttpClient包装器
消息总线 组件间通信 BehaviorSubject + Observable
状态管理 应用状态集中管理 类似Redux的模式
工具服务 通用工具函数集合 日期格式化、验证等
配置服务 管理应用配置 环境变量、API端点等
认证服务 用户认证和授权 登录、注册、权限检查
本地存储服务 浏览器存储封装 localStorage/sessionStorage

本章总结

通过本章学习,你应该掌握了:

  • 服务创建:使用@Injectable装饰器创建服务
  • 依赖注入:理解Angular的DI机制和注入器层级
  • 服务提供:根级、模块级、组件级等不同提供方式
  • 服务生命周期:管理服务的创建、使用和销毁
  • 组件通信:使用服务实现组件间的数据共享
  • RxJS集成:使用Subject和Observable进行状态管理
  • 最佳实践:编写可维护、高效的服务代码