1. 配置 HttpClient
Angular 的 HttpClient 是内置的 HTTP 通信模块,基于 RxJS Observable。在 Standalone 应用中,通过 provideHttpClient() 注册。
// app.config.ts
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { authInterceptor } from './interceptors/auth.interceptor';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes),
provideHttpClient(
withInterceptors([authInterceptor]) // 注册拦截器
),
]
};
2. HttpClient CRUD 操作
import { Injectable, inject } from '@angular/core';
import { HttpClient, HttpParams, HttpHeaders } from '@angular/common/http';
import { Observable } from 'rxjs';
import { map, catchError, retry } from 'rxjs/operators';
import { environment } from '../environments/environment';
export interface Post {
id: number;
title: string;
content: string;
authorId: number;
createdAt: string;
}
@Injectable({ providedIn: 'root' })
export class PostService {
private http = inject(HttpClient);
private baseUrl = `${environment.apiUrl}/posts`;
// GET 列表(带查询参数)
getPosts(page = 1, pageSize = 10): Observable<Post[]> {
const params = new HttpParams()
.set('page', page)
.set('pageSize', pageSize);
return this.http.get<Post[]>(this.baseUrl, { params }).pipe(
retry(2), // 失败时自动重试 2 次
catchError(this.handleError)
);
}
// GET 单个
getPost(id: number): Observable<Post> {
return this.http.get<Post>(`${this.baseUrl}/${id}`);
}
// POST 创建
createPost(data: Omit<Post, 'id' | 'createdAt'>): Observable<Post> {
return this.http.post<Post>(this.baseUrl, data);
}
// PUT 更新(全量替换)
updatePost(id: number, data: Partial<Post>): Observable<Post> {
return this.http.put<Post>(`${this.baseUrl}/${id}`, data);
}
// PATCH 局部更新
patchPost(id: number, data: Partial<Post>): Observable<Post> {
return this.http.patch<Post>(`${this.baseUrl}/${id}`, data);
}
// DELETE
deletePost(id: number): Observable<void> {
return this.http.delete<void>(`${this.baseUrl}/${id}`);
}
private handleError(error: any): Observable<never> {
console.error('API 错误:', error);
throw error;
}
}
3. RxJS 常用操作符
map — 转换数据
import { map } from 'rxjs/operators';
this.http.get<ApiResponse<User[]>>('/api/users').pipe(
// 从 { data: User[], total: number } 中提取 data
map(response => response.data)
).subscribe(users => this.users = users);
catchError — 错误处理
import { catchError, of, EMPTY } from 'rxjs';
this.postService.getPost(id).pipe(
catchError(err => {
if (err.status === 404) {
this.router.navigate(['/not-found']);
return EMPTY; // 不发出任何值
}
return of(null); // 返回默认值
})
).subscribe(post => this.post = post);
switchMap — 切换内层 Observable
import { switchMap } from 'rxjs/operators';
import { ActivatedRoute } from '@angular/router';
// 路由参数变化时,取消前一次请求,发起新请求
this.route.params.pipe(
switchMap(params => this.postService.getPost(+params['id']))
).subscribe(post => this.post = post);
debounceTime — 搜索防抖
import { debounceTime, distinctUntilChanged, switchMap } from 'rxjs/operators';
import { Subject } from 'rxjs';
export class SearchComponent {
private searchSubject = new Subject<string>();
results$ = this.searchSubject.pipe(
debounceTime(300), // 停止输入 300ms 后才发起请求
distinctUntilChanged(), // 值未变化则不重复请求
switchMap(query => // 切换到最新请求
this.searchService.search(query)
)
);
onSearch(event: Event) {
this.searchSubject.next((event.target as HTMLInputElement).value);
}
}
4. HTTP 拦截器
拦截器是 HTTP 请求/响应管道的中间件,可在请求发出前(添加 token、日志)或响应到达后(处理错误、转换数据)统一处理。
// auth.interceptor.ts — 函数式拦截器(Angular 15+)
import { HttpInterceptorFn, HttpRequest, HttpHandlerFn } from '@angular/common/http';
import { inject } from '@angular/core';
import { catchError, throwError } from 'rxjs';
import { AuthService } from '../services/auth.service';
export const authInterceptor: HttpInterceptorFn = (req, next) => {
const auth = inject(AuthService);
const token = localStorage.getItem('token');
// 如果有 token,克隆请求并添加 Authorization 头
const authReq = token
? req.clone({
headers: req.headers.set('Authorization', `Bearer ${token}`)
})
: req;
return next(authReq).pipe(
catchError(err => {
if (err.status === 401) {
// Token 过期,清除登录状态
auth.logout();
}
return throwError(() => err);
})
);
};
// loading.interceptor.ts — 全局 loading 状态
export const loadingInterceptor: HttpInterceptorFn = (req, next) => {
const loading = inject(LoadingService);
loading.show();
return next(req).pipe(
finalize(() => loading.hide())
);
};
5. 环境变量配置
// src/environments/environment.ts(开发环境)
export const environment = {
production: false,
apiUrl: 'http://localhost:3000/api',
wsUrl: 'ws://localhost:3000',
};
// src/environments/environment.prod.ts(生产环境)
export const environment = {
production: true,
apiUrl: 'https://api.myapp.com/api',
wsUrl: 'wss://api.myapp.com',
};
// 在 angular.json 的 fileReplacements 中配置替换规则
// ng build --configuration production 自动替换
6. 实战:数据列表 CRUD 组件
@Component({
selector: 'app-post-list',
standalone: true,
imports: [AsyncPipe, DatePipe],
template: `
<div class="toolbar">
<button (click)="loadPosts()">刷新</button>
<button (click)="openCreateDialog()">新建</button>
</div>
@if (loading()) {
<app-spinner />
} @else if (error()) {
<div class="error">{{ error() }}</div>
} @else {
<table>
@for (post of posts(); track post.id) {
<tr>
<td>{{ post.title }}</td>
<td>{{ post.createdAt | date:'yyyy-MM-dd' }}</td>
<td>
<button (click)="editPost(post)">编辑</button>
<button (click)="deletePost(post.id)">删除</button>
</td>
</tr>
}
</table>
}
`
})
export class PostListComponent implements OnInit {
private postService = inject(PostService);
posts = signal<Post[]>([]);
loading = signal(false);
error = signal<string | null>(null);
ngOnInit() { this.loadPosts(); }
loadPosts() {
this.loading.set(true);
this.error.set(null);
this.postService.getPosts().subscribe({
next: posts => this.posts.set(posts),
error: err => this.error.set(err.message),
complete: () => this.loading.set(false)
});
}
deletePost(id: number) {
if (!confirm('确定删除?')) return;
this.postService.deletePost(id).subscribe(() => {
this.posts.update(posts => posts.filter(p => p.id !== id));
});
}
}
本章小结:Angular HttpClient 提供类型安全的 HTTP 通信。拦截器统一处理认证和错误,RxJS 操作符链使数据转换和异步控制更优雅。下一章深入 Angular 表单系统。