07 - HTTP通信
📖 学习目标
通过本章节学习,您将掌握:
- HTTP客户端的使用
- RESTful API调用
- 请求和响应处理
- 错误处理机制
- 拦截器的使用
- 缓存策略
- 文件上传下载
🎯 核心概念
1. HttpClient模块
Angular的HttpClient提供了强大的HTTP客户端功能:
- 基于Observable:所有HTTP操作返回Observable
- 类型安全:支持TypeScript类型检查
- 拦截器支持:可以拦截请求和响应
- 错误处理:内置错误处理机制
- 进度支持:支持上传下载进度
2. HTTP方法
typescript
// 常用HTTP方法
GET // 获取数据
POST // 创建数据
PUT // 更新数据
PATCH // 部分更新
DELETE // 删除数据
HEAD // 获取响应头
OPTIONS // 获取支持的HTTP方法
3. 请求配置
typescript
interface HttpRequestConfig {
headers?: HttpHeaders;
params?: HttpParams;
responseType?: 'json' | 'text' | 'blob' | 'arraybuffer';
observe?: 'body' | 'response' | 'events';
reportProgress?: boolean;
withCredentials?: boolean;
}
🏗️ 基础HTTP操作
1. 配置HttpClient
typescript
// app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http';
@NgModule({
imports: [
BrowserModule,
HttpClientModule // 导入HttpClient模块
],
// ...
})
export class AppModule { }
2. 基本HTTP请求
typescript
// api.service.ts
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
import { Observable } from 'rxjs';
export interface User {
id: number;
name: string;
email: string;
role: string;
}
export interface ApiResponse<T> {
data: T;
message: string;
success: boolean;
}
@Injectable({
providedIn: 'root'
})
export class ApiService {
private baseUrl = 'https://api.example.com';
constructor(private http: HttpClient) {}
// GET请求 - 获取所有用户
getUsers(): Observable<ApiResponse<User[]>> {
return this.http.get<ApiResponse<User[]>>(`${this.baseUrl}/users`);
}
// GET请求 - 根据ID获取用户
getUserById(id: number): Observable<ApiResponse<User>> {
return this.http.get<ApiResponse<User>>(`${this.baseUrl}/users/${id}`);
}
// POST请求 - 创建用户
createUser(user: Partial<User>): Observable<ApiResponse<User>> {
const headers = new HttpHeaders({
'Content-Type': 'application/json'
});
return this.http.post<ApiResponse<User>>(
`${this.baseUrl}/users`,
user,
{ headers }
);
}
// PUT请求 - 更新用户
updateUser(id: number, user: Partial<User>): Observable<ApiResponse<User>> {
return this.http.put<ApiResponse<User>>(
`${this.baseUrl}/users/${id}`,
user
);
}
// DELETE请求 - 删除用户
deleteUser(id: number): Observable<ApiResponse<null>> {
return this.http.delete<ApiResponse<null>>(`${this.baseUrl}/users/${id}`);
}
// 带查询参数的GET请求
searchUsers(query: string, page: number = 1, limit: number = 10): Observable<ApiResponse<User[]>> {
let params = new HttpParams()
.set('q', query)
.set('page', page.toString())
.set('limit', limit.toString());
return this.http.get<ApiResponse<User[]>>(`${this.baseUrl}/users/search`, { params });
}
}
🔧 高级HTTP功能
1. 请求拦截器
typescript
// auth.interceptor.ts
import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http';
import { Observable } from 'rxjs';
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
// 获取token
const token = localStorage.getItem('auth_token');
if (token) {
// 克隆请求并添加认证头
const authReq = req.clone({
headers: req.headers.set('Authorization', `Bearer ${token}`)
});
return next.handle(authReq);
}
return next.handle(req);
}
}
// 注册拦截器
@NgModule({
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: AuthInterceptor,
multi: true
}
]
})
export class AppModule {}
2. 响应拦截器
typescript
// response.interceptor.ts
import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpResponse } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { map, catchError } from 'rxjs/operators';
@Injectable()
export class ResponseInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(req).pipe(
map((event: HttpEvent<any>) => {
if (event instanceof HttpResponse) {
// 统一处理响应格式
if (event.body && event.body.success === false) {
throw new Error(event.body.message || '请求失败');
}
}
return event;
}),
catchError(error => {
// 统一错误处理
console.error('HTTP Error:', error);
return throwError(error);
})
);
}
}
3. 错误处理
typescript
// error-handler.service.ts
import { Injectable } from '@angular/core';
import { HttpErrorResponse } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class ErrorHandlerService {
handleError(error: HttpErrorResponse): Observable<never> {
let errorMessage = '发生未知错误';
if (error.error instanceof ErrorEvent) {
// 客户端错误
errorMessage = `客户端错误: ${error.error.message}`;
} else {
// 服务器错误
switch (error.status) {
case 400:
errorMessage = '请求参数错误';
break;
case 401:
errorMessage = '未授权,请重新登录';
break;
case 403:
errorMessage = '禁止访问';
break;
case 404:
errorMessage = '请求的资源不存在';
break;
case 500:
errorMessage = '服务器内部错误';
break;
default:
errorMessage = `服务器错误: ${error.status}`;
}
}
console.error('Error:', errorMessage);
return throwError(() => new Error(errorMessage));
}
}
// 在服务中使用
@Injectable({
providedIn: 'root'
})
export class UserService {
constructor(
private http: HttpClient,
private errorHandler: ErrorHandlerService
) {}
getUsers(): Observable<User[]> {
return this.http.get<User[]>('/api/users').pipe(
catchError(this.errorHandler.handleError.bind(this.errorHandler))
);
}
}
📁 文件操作
1. 文件上传
typescript
// file-upload.service.ts
import { Injectable } from '@angular/core';
import { HttpClient, HttpEvent, HttpEventType, HttpProgressEvent } from '@angular/common/http';
import { Observable } from 'rxjs';
import { map, filter } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class FileUploadService {
constructor(private http: HttpClient) {}
// 单文件上传
uploadFile(file: File): Observable<{ progress: number; result?: any }> {
const formData = new FormData();
formData.append('file', file);
return this.http.post('/api/upload', formData, {
reportProgress: true,
observe: 'events'
}).pipe(
map((event: HttpEvent<any>) => {
switch (event.type) {
case HttpEventType.UploadProgress:
const progress = Math.round(100 * event.loaded / (event.total || 1));
return { progress };
case HttpEventType.Response:
return { progress: 100, result: event.body };
default:
return { progress: 0 };
}
}),
filter(event => event.progress > 0)
);
}
// 多文件上传
uploadMultipleFiles(files: File[]): Observable<{ progress: number; results?: any[] }> {
const formData = new FormData();
files.forEach((file, index) => {
formData.append(`files[${index}]`, file);
});
return this.http.post('/api/upload/multiple', formData, {
reportProgress: true,
observe: 'events'
}).pipe(
map((event: HttpEvent<any>) => {
switch (event.type) {
case HttpEventType.UploadProgress:
const progress = Math.round(100 * event.loaded / (event.total || 1));
return { progress };
case HttpEventType.Response:
return { progress: 100, results: event.body };
default:
return { progress: 0 };
}
}),
filter(event => event.progress > 0)
);
}
}
2. 文件下载
typescript
// file-download.service.ts
import { Injectable } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class FileDownloadService {
constructor(private http: HttpClient) {}
// 下载文件
downloadFile(url: string, filename?: string): Observable<Blob> {
return this.http.get(url, {
responseType: 'blob'
}).pipe(
map(blob => {
// 创建下载链接
const downloadUrl = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = downloadUrl;
link.download = filename || 'download';
link.click();
window.URL.revokeObjectURL(downloadUrl);
return blob;
})
);
}
// 预览文件
previewFile(url: string): Observable<string> {
return this.http.get(url, {
responseType: 'blob'
}).pipe(
map(blob => {
return window.URL.createObjectURL(blob);
})
);
}
}
🎮 实践练习
练习1:创建完整的API服务
创建一个完整的用户管理API服务,包含:
- 用户CRUD操作
- 分页查询
- 搜索功能
- 错误处理
- 加载状态管理
练习2:实现文件上传组件
创建一个文件上传组件,支持:
- 单文件/多文件上传
- 上传进度显示
- 文件类型验证
- 文件大小限制
- 拖拽上传
📚 详细示例
完整的用户管理服务
typescript
// user-api.service.ts
import { Injectable } from '@angular/core';
import { HttpClient, HttpParams, HttpErrorResponse } from '@angular/common/http';
import { Observable, BehaviorSubject, throwError } from 'rxjs';
import { map, catchError, tap } from 'rxjs/operators';
export interface User {
id: number;
name: string;
email: string;
role: string;
avatar?: string;
createdAt: Date;
updatedAt: Date;
}
export interface UserListResponse {
users: User[];
total: number;
page: number;
limit: number;
totalPages: number;
}
export interface UserQueryParams {
page?: number;
limit?: number;
search?: string;
role?: string;
sortBy?: string;
sortOrder?: 'asc' | 'desc';
}
@Injectable({
providedIn: 'root'
})
export class UserApiService {
private baseUrl = '/api/users';
private loadingSubject = new BehaviorSubject<boolean>(false);
private errorSubject = new BehaviorSubject<string | null>(null);
public loading$ = this.loadingSubject.asObservable();
public error$ = this.errorSubject.asObservable();
constructor(private http: HttpClient) {}
// 获取用户列表
getUsers(params: UserQueryParams = {}): Observable<UserListResponse> {
this.setLoading(true);
this.clearError();
let httpParams = new HttpParams();
if (params.page) httpParams = httpParams.set('page', params.page.toString());
if (params.limit) httpParams = httpParams.set('limit', params.limit.toString());
if (params.search) httpParams = httpParams.set('search', params.search);
if (params.role) httpParams = httpParams.set('role', params.role);
if (params.sortBy) httpParams = httpParams.set('sortBy', params.sortBy);
if (params.sortOrder) httpParams = httpParams.set('sortOrder', params.sortOrder);
return this.http.get<UserListResponse>(this.baseUrl, { params: httpParams })
.pipe(
tap(() => this.setLoading(false)),
catchError(this.handleError.bind(this))
);
}
// 根据ID获取用户
getUserById(id: number): Observable<User> {
this.setLoading(true);
this.clearError();
return this.http.get<User>(`${this.baseUrl}/${id}`)
.pipe(
tap(() => this.setLoading(false)),
catchError(this.handleError.bind(this))
);
}
// 创建用户
createUser(userData: Partial<User>): Observable<User> {
this.setLoading(true);
this.clearError();
return this.http.post<User>(this.baseUrl, userData)
.pipe(
tap(() => this.setLoading(false)),
catchError(this.handleError.bind(this))
);
}
// 更新用户
updateUser(id: number, userData: Partial<User>): Observable<User> {
this.setLoading(true);
this.clearError();
return this.http.put<User>(`${this.baseUrl}/${id}`, userData)
.pipe(
tap(() => this.setLoading(false)),
catchError(this.handleError.bind(this))
);
}
// 删除用户
deleteUser(id: number): Observable<void> {
this.setLoading(true);
this.clearError();
return this.http.delete<void>(`${this.baseUrl}/${id}`)
.pipe(
tap(() => this.setLoading(false)),
catchError(this.handleError.bind(this))
);
}
// 批量删除用户
deleteUsers(ids: number[]): Observable<void> {
this.setLoading(true);
this.clearError();
return this.http.delete<void>(`${this.baseUrl}/batch`, {
body: { ids }
}).pipe(
tap(() => this.setLoading(false)),
catchError(this.handleError.bind(this))
);
}
// 上传用户头像
uploadAvatar(userId: number, file: File): Observable<{ avatarUrl: string }> {
this.setLoading(true);
this.clearError();
const formData = new FormData();
formData.append('avatar', file);
return this.http.post<{ avatarUrl: string }>(`${this.baseUrl}/${userId}/avatar`, formData)
.pipe(
tap(() => this.setLoading(false)),
catchError(this.handleError.bind(this))
);
}
// 导出用户数据
exportUsers(format: 'csv' | 'excel' = 'csv'): Observable<Blob> {
this.setLoading(true);
this.clearError();
return this.http.get(`${this.baseUrl}/export`, {
params: new HttpParams().set('format', format),
responseType: 'blob'
}).pipe(
tap(() => this.setLoading(false)),
catchError(this.handleError.bind(this))
);
}
private setLoading(loading: boolean): void {
this.loadingSubject.next(loading);
}
private setError(error: string): void {
this.errorSubject.next(error);
}
private clearError(): void {
this.errorSubject.next(null);
}
private handleError(error: HttpErrorResponse): Observable<never> {
this.setLoading(false);
let errorMessage = '发生未知错误';
if (error.error instanceof ErrorEvent) {
errorMessage = `客户端错误: ${error.error.message}`;
} else {
switch (error.status) {
case 400:
errorMessage = error.error?.message || '请求参数错误';
break;
case 401:
errorMessage = '未授权,请重新登录';
break;
case 403:
errorMessage = '没有权限执行此操作';
break;
case 404:
errorMessage = '请求的资源不存在';
break;
case 409:
errorMessage = '数据冲突,请检查输入';
break;
case 422:
errorMessage = '数据验证失败';
break;
case 500:
errorMessage = '服务器内部错误';
break;
default:
errorMessage = `服务器错误: ${error.status}`;
}
}
this.setError(errorMessage);
return throwError(() => new Error(errorMessage));
}
}
使用API服务的组件
typescript
// user-management.component.ts
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subject, Subscription } from 'rxjs';
import { debounceTime, distinctUntilChanged, switchMap } from 'rxjs/operators';
import { UserApiService, User, UserQueryParams } from './user-api.service';
@Component({
selector: 'app-user-management',
templateUrl: './user-management.component.html'
})
export class UserManagementComponent implements OnInit, OnDestroy {
users: User[] = [];
loading = false;
error: string | null = null;
// 分页信息
currentPage = 1;
pageSize = 10;
totalItems = 0;
totalPages = 0;
// 搜索
searchTerm = '';
private searchSubject = new Subject<string>();
// 订阅管理
private subscriptions: Subscription[] = [];
constructor(private userApiService: UserApiService) {}
ngOnInit() {
this.loadUsers();
this.setupSearch();
this.subscribeToLoading();
this.subscribeToError();
}
ngOnDestroy() {
this.subscriptions.forEach(sub => sub.unsubscribe());
}
loadUsers() {
const params: UserQueryParams = {
page: this.currentPage,
limit: this.pageSize,
search: this.searchTerm || undefined
};
this.subscriptions.push(
this.userApiService.getUsers(params).subscribe({
next: (response) => {
this.users = response.users;
this.totalItems = response.total;
this.totalPages = response.totalPages;
},
error: (error) => {
console.error('加载用户失败:', error);
}
})
);
}
onSearch(searchTerm: string) {
this.searchSubject.next(searchTerm);
}
onPageChange(page: number) {
this.currentPage = page;
this.loadUsers();
}
createUser() {
// 打开创建用户对话框
// 这里可以调用模态框或导航到创建页面
}
editUser(user: User) {
// 打开编辑用户对话框
// 这里可以调用模态框或导航到编辑页面
}
deleteUser(user: User) {
if (confirm(`确定要删除用户 "${user.name}" 吗?`)) {
this.subscriptions.push(
this.userApiService.deleteUser(user.id).subscribe({
next: () => {
this.loadUsers(); // 重新加载列表
},
error: (error) => {
console.error('删除用户失败:', error);
}
})
);
}
}
uploadAvatar(user: User, event: any) {
const file = event.target.files[0];
if (file) {
this.subscriptions.push(
this.userApiService.uploadAvatar(user.id, file).subscribe({
next: (response) => {
user.avatar = response.avatarUrl;
},
error: (error) => {
console.error('上传头像失败:', error);
}
})
);
}
}
exportUsers() {
this.subscriptions.push(
this.userApiService.exportUsers('csv').subscribe({
next: (blob) => {
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = 'users.csv';
link.click();
window.URL.revokeObjectURL(url);
},
error: (error) => {
console.error('导出失败:', error);
}
})
);
}
private setupSearch() {
this.subscriptions.push(
this.searchSubject.pipe(
debounceTime(300),
distinctUntilChanged(),
switchMap(searchTerm => {
this.searchTerm = searchTerm;
this.currentPage = 1;
this.loadUsers();
return [];
})
).subscribe()
);
}
private subscribeToLoading() {
this.subscriptions.push(
this.userApiService.loading$.subscribe(loading => {
this.loading = loading;
})
);
}
private subscribeToError() {
this.subscriptions.push(
this.userApiService.error$.subscribe(error => {
this.error = error;
})
);
}
}
🔧 缓存策略
1. HTTP缓存
typescript
// cache.service.ts
import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { tap } from 'rxjs/operators';
@Injectable()
export class CacheInterceptor implements HttpInterceptor {
private cache = new Map<string, any>();
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
// 只缓存GET请求
if (req.method !== 'GET') {
return next.handle(req);
}
const cachedResponse = this.cache.get(req.url);
if (cachedResponse) {
return of(cachedResponse);
}
return next.handle(req).pipe(
tap(event => {
if (event.type === 4) { // HttpResponse
this.cache.set(req.url, event);
}
})
);
}
}
2. 服务端缓存
typescript
// cached-api.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { map, tap } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class CachedApiService {
private cache = new Map<string, { data: any; timestamp: number }>();
private cacheTimeout = 5 * 60 * 1000; // 5分钟
constructor(private http: HttpClient) {}
get<T>(url: string, useCache: boolean = true): Observable<T> {
if (useCache) {
const cached = this.cache.get(url);
if (cached && Date.now() - cached.timestamp < this.cacheTimeout) {
return of(cached.data);
}
}
return this.http.get<T>(url).pipe(
tap(data => {
this.cache.set(url, {
data,
timestamp: Date.now()
});
})
);
}
clearCache(url?: string): void {
if (url) {
this.cache.delete(url);
} else {
this.cache.clear();
}
}
}
✅ 学习检查
完成本章节后,请确认您能够:
- [ ] 使用HttpClient进行HTTP请求
- [ ] 处理请求和响应
- [ ] 实现错误处理机制
- [ ] 使用拦截器
- [ ] 处理文件上传下载
- [ ] 实现缓存策略
🚀 下一步
完成本章节学习后,请继续学习08-路由基础。