本章目标:深入理解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进行状态管理
- 最佳实践:编写可维护、高效的服务代码