Skip to content
 

Angular HTTP 客户端

更新: 10/9/2025字数: 0 字 时长: 0 分钟

HTTP 客户端是现代前端应用与后端服务通信的核心工具。Angular 提供了强大的HttpClient模块来处理所有 HTTP 请求。

一、HttpClient 基础

1. 配置 HttpClient

首先需要在模块中导入HttpClientModule

typescript
import { HttpClientModule } from "@angular/common/http";

@NgModule({
  imports: [HttpClientModule]
})
export class AppModule {}

然后在服务中注入HttpClient

typescript
import { HttpClient } from "@angular/common/http";

@Injectable({ providedIn: "root" })
export class DataService {
  constructor(private http: HttpClient) {}
}

2. 基本请求方法

HttpClient 提供了对应 HTTP 方法的快捷方式:

typescript
// GET请求
getUsers(): Observable<User[]> {
  return this.http.get<User[]>('/api/users');
}

// POST请求
addUser(user: User): Observable<User> {
  return this.http.post<User>('/api/users', user);
}

// PUT请求
updateUser(id: number, user: User): Observable<User> {
  return this.http.put<User>(`/api/users/${id}`, user);
}

// DELETE请求
deleteUser(id: number): Observable<void> {
  return this.http.delete<void>(`/api/users/${id}`);
}

二、请求和响应处理

1. 请求配置选项

可以传递配置对象作为最后一个参数:

typescript
// 带查询参数的GET请求
searchUsers(term: string): Observable<User[]> {
  return this.http.get<User[]>('/api/users/search', {
    params: { q: term }
  });
}

// 自定义请求头
getProtectedData(): Observable<Data> {
  return this.http.get<Data>('/api/protected', {
    headers: {
      'Authorization': 'Bearer token'
    }
  });
}

2. 响应类型处理

Angular 会自动将 JSON 响应转换为 JavaScript 对象:

typescript
// 获取完整响应(包括headers等)
getUserWithHeaders(id: number): Observable<HttpResponse<User>> {
  return this.http.get<User>(`/api/users/${id}`, {
    observe: 'response'
  });
}

// 获取响应文本(非JSON)
getPlainText(): Observable<string> {
  return this.http.get('/api/text', { responseType: 'text' });
}

三、错误处理

1. 基本错误捕获

typescript
getUsers(): Observable<User[]> {
  return this.http.get<User[]>('/api/users').pipe(
    catchError((error: HttpErrorResponse) => {
      console.error('请求失败:', error.message);
      return throwError('发生错误,请重试');
    })
  );
}

2. 全局错误拦截器

创建自定义拦截器:

typescript
@Injectable()
export class ErrorInterceptor implements HttpInterceptor {
  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return next.handle(req).pipe(
      catchError((error: HttpErrorResponse) => {
        let errorMsg = "";
        if (error.error instanceof ErrorEvent) {
          // 客户端错误
          errorMsg = `客户端错误: ${error.error.message}`;
        } else {
          // 服务端错误
          errorMsg = `服务端错误: ${error.status} - ${error.message}`;
        }
        console.error(errorMsg);
        return throwError(errorMsg);
      })
    );
  }
}

注册拦截器:

typescript
@NgModule({
  providers: [{ provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true }]
})
export class AppModule {}

四、高级特性

1. 请求拦截

typescript
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  constructor(private auth: AuthService) {}

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const authToken = this.auth.getToken();
    const authReq = req.clone({
      headers: req.headers.set("Authorization", `Bearer ${authToken}`)
    });
    return next.handle(authReq);
  }
}

2. 进度事件

typescript
uploadFile(file: File): Observable<HttpEvent<any>> {
  const formData = new FormData();
  formData.append('file', file);

  return this.http.post('/api/upload', formData, {
    reportProgress: true,
    observe: 'events'
  }).pipe(
    tap(event => {
      if (event.type === HttpEventType.UploadProgress) {
        const percentDone = Math.round(100 * event.loaded / (event.total || 1));
        console.log(`上传进度: ${percentDone}%`);
      }
    })
  );
}

3. 取消请求

typescript
private cancelRequest = new Subject<void>();

search(term: string): Observable<Result[]> {
  // 取消之前的请求
  this.cancelRequest.next();

  return this.http.get<Result[]>('/api/search', {
    params: { q: term }
  }).pipe(
    takeUntil(this.cancelRequest)
  );
}

五、最佳实践

1. 服务封装模式

typescript
@Injectable({ providedIn: "root" })
export class ApiService {
  private readonly apiUrl = environment.apiUrl;

  constructor(private http: HttpClient) {}

  private getFullUrl(path: string): string {
    return `${this.apiUrl}/${path}`;
  }

  get<T>(path: string, params?: HttpParams): Observable<T> {
    return this.http.get<T>(this.getFullUrl(path), { params });
  }

  post<T>(path: string, body: any): Observable<T> {
    return this.http.post<T>(this.getFullUrl(path), body);
  }

  // 其他HTTP方法...
}

2. 类型安全 API

typescript
interface UserApi {
  getUsers(): Observable<User[]>;
  getUser(id: number): Observable<User>;
  createUser(user: User): Observable<User>;
}

@Injectable({ providedIn: "root" })
export class UserService implements UserApi {
  private readonly basePath = "users";

  constructor(private api: ApiService) {}

  getUsers(): Observable<User[]> {
    return this.api.get<User[]>(this.basePath);
  }

  getUser(id: number): Observable<User> {
    return this.api.get<User>(`${this.basePath}/${id}`);
  }

  createUser(user: User): Observable<User> {
    return this.api.post<User>(this.basePath, user);
  }
}

3. 测试策略

typescript
describe("UserService", () => {
  let service: UserService;
  let httpMock: HttpTestingController;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule],
      providers: [UserService]
    });

    service = TestBed.inject(UserService);
    httpMock = TestBed.inject(HttpTestingController);
  });

  afterEach(() => {
    httpMock.verify(); // 验证没有未处理的请求
  });

  it("应该获取用户列表", () => {
    const mockUsers = [{ id: 1, name: "测试用户" }];

    service.getUsers().subscribe((users) => {
      expect(users).toEqual(mockUsers);
    });

    const req = httpMock.expectOne("api/users");
    expect(req.request.method).toBe("GET");
    req.flush(mockUsers);
  });
});

六、常见问题解答

Q1: HttpClient 和传统的 Http 有什么区别?

  • HttpClient 是 Http 的升级版,提供更强大的功能
  • 内置 JSON 解析
  • 支持拦截器
  • 类型化的响应
  • 更好的错误处理机制

Q2: 如何处理跨域请求(CORS)?

  • 服务端需要配置 CORS 头
  • 开发环境可以配置代理
  • 生产环境确保服务端支持跨域

Q3: 如何上传文件?

  • 使用 FormData 对象
  • 设置Content-Type: multipart/form-data
  • 监听进度事件

我见青山多妩媚,料青山见我应如是。