服务基础 #
一、什么是服务 #
服务是一个广义的概念,用于封装业务逻辑、数据访问、工具函数等。服务可以被多个组件共享,实现代码复用和关注点分离。
text
┌─────────────────────────────────────────────────────┐
│ Angular 应用 │
├─────────────────────────────────────────────────────┤
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ 组件A │ │ 组件B │ │ 组件C │ │
│ └────┬────┘ └────┬────┘ └────┬────┘ │
│ │ │ │ │
│ └────────────┼────────────┘ │
│ ↓ │
│ ┌──────────────┐ │
│ │ 服务 │ │
│ │ 共享逻辑 │ │
│ └──────────────┘ │
└─────────────────────────────────────────────────────┘
二、创建服务 #
2.1 使用CLI创建 #
bash
ng generate service services/user
# 简写
ng g s services/user
2.2 服务基本结构 #
typescript
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class UserService {
constructor() { }
getUsers(): User[] {
return [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' }
];
}
}
2.3 @Injectable装饰器 #
typescript
@Injectable({
providedIn: 'root' // 在根模块中提供,全局单例
})
export class UserService { }
@Injectable({
providedIn: 'any' // 每个懒加载模块单独实例
})
export class FeatureService { }
@Injectable({
providedIn: UserModule // 在指定模块中提供
})
export class ModuleService { }
三、服务注册方式 #
3.1 providedIn: ‘root’(推荐) #
typescript
@Injectable({
providedIn: 'root'
})
export class UserService {
// 全局单例,应用启动时创建
}
3.2 模块级别注册 #
typescript
@NgModule({
providers: [UserService]
})
export class UserModule { }
3.3 组件级别注册 #
typescript
@Component({
selector: 'app-user',
template: '...',
providers: [UserService] // 每个组件实例独立的服务实例
})
export class UserComponent { }
3.4 注册方式对比 #
| 方式 | 作用域 | 实例数量 | 适用场景 |
|---|---|---|---|
providedIn: 'root' |
全局 | 单例 | 全局共享服务 |
| 模块级别 | 模块内 | 单例 | 模块内共享 |
| 组件级别 | 组件内 | 每组件一个 | 组件独立状态 |
四、使用服务 #
4.1 在组件中使用 #
typescript
import { Component, OnInit } from '@angular/core';
import { UserService } from '../services/user.service';
@Component({
selector: 'app-user-list',
template: `
<ul>
<li *ngFor="let user of users">{{ user.name }}</li>
</ul>
`
})
export class UserListComponent implements OnInit {
users: User[] = [];
constructor(private userService: UserService) {}
ngOnInit() {
this.users = this.userService.getUsers();
}
}
4.2 服务间依赖 #
typescript
@Injectable({
providedIn: 'root'
})
export class ApiService {
constructor(private http: HttpClient) {}
get<T>(url: string) {
return this.http.get<T>(url);
}
}
@Injectable({
providedIn: 'root'
})
export class UserService {
constructor(private apiService: ApiService) {}
getUsers() {
return this.apiService.get<User[]>('/api/users');
}
}
五、服务类型示例 #
5.1 数据服务 #
typescript
@Injectable({
providedIn: 'root'
})
export class DataService {
private dataSubject = new BehaviorSubject<any[]>([]);
data$ = this.dataSubject.asObservable();
private loadingSubject = new BehaviorSubject<boolean>(false);
loading$ = this.loadingSubject.asObservable();
constructor(private http: HttpClient) {}
loadData() {
this.loadingSubject.next(true);
this.http.get<any[]>('/api/data').subscribe({
next: (data) => {
this.dataSubject.next(data);
this.loadingSubject.next(false);
},
error: (error) => {
this.loadingSubject.next(false);
console.error(error);
}
});
}
addItem(item: any) {
const currentData = this.dataSubject.getValue();
this.dataSubject.next([...currentData, item]);
}
removeItem(id: number) {
const currentData = this.dataSubject.getValue();
this.dataSubject.next(currentData.filter(item => item.id !== id));
}
}
5.2 配置服务 #
typescript
@Injectable({
providedIn: 'root'
})
export class ConfigService {
private config: AppConfig;
constructor() {
this.config = {
apiUrl: 'https://api.example.com',
timeout: 30000,
pageSize: 10
};
}
get apiUrl(): string {
return this.config.apiUrl;
}
get timeout(): number {
return this.config.timeout;
}
get pageSize(): number {
return this.config.pageSize;
}
updateConfig(newConfig: Partial<AppConfig>) {
this.config = { ...this.config, ...newConfig };
}
}
5.3 日志服务 #
typescript
@Injectable({
providedIn: 'root'
})
export class LogService {
private logs: LogEntry[] = [];
log(message: string, level: 'info' | 'warn' | 'error' = 'info') {
const entry: LogEntry = {
timestamp: new Date(),
message,
level
};
this.logs.push(entry);
switch (level) {
case 'info':
console.log(`[INFO] ${message}`);
break;
case 'warn':
console.warn(`[WARN] ${message}`);
break;
case 'error':
console.error(`[ERROR] ${message}`);
break;
}
}
getLogs(): LogEntry[] {
return [...this.logs];
}
clearLogs() {
this.logs = [];
}
}
5.4 认证服务 #
typescript
@Injectable({
providedIn: 'root'
})
export class AuthService {
private currentUserSubject = new BehaviorSubject<User | null>(null);
currentUser$ = this.currentUserSubject.asObservable();
private isAuthenticatedSubject = new BehaviorSubject<boolean>(false);
isAuthenticated$ = this.isAuthenticatedSubject.asObservable();
constructor(
private http: HttpClient,
private router: Router
) {
this.checkAuth();
}
private checkAuth() {
const token = localStorage.getItem('token');
if (token) {
this.validateToken(token);
}
}
login(credentials: LoginCredentials): Observable<User> {
return this.http.post<AuthResponse>('/api/auth/login', credentials).pipe(
tap(response => {
localStorage.setItem('token', response.token);
this.currentUserSubject.next(response.user);
this.isAuthenticatedSubject.next(true);
}),
map(response => response.user)
);
}
logout() {
localStorage.removeItem('token');
this.currentUserSubject.next(null);
this.isAuthenticatedSubject.next(false);
this.router.navigate(['/login']);
}
get currentUser(): User | null {
return this.currentUserSubject.getValue();
}
get isAuthenticated(): boolean {
return this.isAuthenticatedSubject.getValue();
}
private validateToken(token: string) {
this.http.get<User>('/api/auth/me').subscribe({
next: (user) => {
this.currentUserSubject.next(user);
this.isAuthenticatedSubject.next(true);
},
error: () => {
this.logout();
}
});
}
}
5.5 缓存服务 #
typescript
@Injectable({
providedIn: 'root'
})
export class CacheService {
private cache = new Map<string, { data: any; expiry: number }>();
private defaultTTL = 5 * 60 * 1000; // 5分钟
get<T>(key: string): T | null {
const cached = this.cache.get(key);
if (!cached) {
return null;
}
if (Date.now() > cached.expiry) {
this.cache.delete(key);
return null;
}
return cached.data as T;
}
set<T>(key: string, data: T, ttl: number = this.defaultTTL) {
this.cache.set(key, {
data,
expiry: Date.now() + ttl
});
}
has(key: string): boolean {
return this.get(key) !== null;
}
delete(key: string) {
this.cache.delete(key);
}
clear() {
this.cache.clear();
}
getOrSet<T>(key: string, factory: () => Observable<T>, ttl?: number): Observable<T> {
const cached = this.get<T>(key);
if (cached !== null) {
return of(cached);
}
return factory().pipe(
tap(data => this.set(key, data, ttl))
);
}
}
六、服务与RxJS #
6.1 使用Observable #
typescript
@Injectable({
providedIn: 'root'
})
export class ProductService {
private productsSubject = new BehaviorSubject<Product[]>([]);
products$ = this.productsSubject.asObservable();
constructor(private http: HttpClient) {}
loadProducts() {
this.http.get<Product[]>('/api/products').subscribe(
products => this.productsSubject.next(products)
);
}
getProduct(id: number): Observable<Product | undefined> {
return this.products$.pipe(
map(products => products.find(p => p.id === id))
);
}
addProduct(product: Product) {
const current = this.productsSubject.getValue();
this.productsSubject.next([...current, product]);
}
}
6.2 使用Signal(Angular 17+) #
typescript
import { Injectable, signal, computed } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class CounterService {
private countSignal = signal(0);
readonly count = this.countSignal.asReadonly();
readonly doubleCount = computed(() => this.countSignal() * 2);
readonly isEven = computed(() => this.countSignal() % 2 === 0);
increment() {
this.countSignal.update(v => v + 1);
}
decrement() {
this.countSignal.update(v => v - 1);
}
reset() {
this.countSignal.set(0);
}
}
七、服务最佳实践 #
7.1 单一职责 #
typescript
// 好的做法:职责单一
@Injectable({ providedIn: 'root' })
export class UserService {
// 只处理用户相关逻辑
}
@Injectable({ providedIn: 'root' })
export class AuthService {
// 只处理认证相关逻辑
}
// 不好的做法:职责过多
@Injectable({ providedIn: 'root' })
export class AppService {
// 处理用户、认证、产品、订单...
}
7.2 使用接口定义类型 #
typescript
interface User {
id: number;
name: string;
email: string;
}
interface UserFilter {
name?: string;
status?: 'active' | 'inactive';
}
@Injectable({ providedIn: 'root' })
export class UserService {
getUsers(filter?: UserFilter): Observable<User[]> {
let params = new HttpParams();
if (filter?.name) {
params = params.set('name', filter.name);
}
if (filter?.status) {
params = params.set('status', filter.status);
}
return this.http.get<User[]>('/api/users', { params });
}
}
7.3 错误处理 #
typescript
@Injectable({ providedIn: 'root' })
export class ApiService {
constructor(private http: HttpClient) {}
get<T>(url: string): Observable<T> {
return this.http.get<T>(url).pipe(
retry(3),
catchError(this.handleError)
);
}
private handleError(error: HttpErrorResponse) {
let errorMessage = '发生未知错误';
if (error.error instanceof ErrorEvent) {
errorMessage = `客户端错误: ${error.error.message}`;
} else {
errorMessage = `服务器错误: ${error.status} - ${error.message}`;
}
return throwError(() => new Error(errorMessage));
}
}
7.4 使用InjectionToken #
typescript
import { InjectionToken } from '@angular/core';
export const API_URL = new InjectionToken<string>('API_URL');
export const APP_CONFIG = new InjectionToken<AppConfig>('APP_CONFIG');
// 在模块中提供
@NgModule({
providers: [
{ provide: API_URL, useValue: 'https://api.example.com' },
{ provide: APP_CONFIG, useValue: { theme: 'light', lang: 'zh-CN' } }
]
})
export class AppModule { }
// 在服务中使用
@Injectable({ providedIn: 'root' })
export class ApiService {
constructor(
@Inject(API_URL) private apiUrl: string,
@Inject(APP_CONFIG) private config: AppConfig
) {}
}
八、总结 #
| 概念 | 说明 |
|---|---|
@Injectable |
标记类为可注入服务 |
providedIn |
指定服务提供范围 |
| 单例服务 | 全局唯一实例 |
| 服务依赖 | 服务间可相互注入 |
| RxJS | 服务中处理异步数据 |
下一步:依赖注入
最后更新:2026-03-26