MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Angular HTTP客户端:与后端API交互

2023-09-292.5k 阅读

Angular HTTP 客户端简介

在现代 Web 应用开发中,前端与后端 API 的交互是至关重要的一环。Angular 作为一款强大的前端框架,提供了 HTTP 客户端模块,使得与后端 API 的通信变得简洁高效。Angular 的 HTTP 客户端基于 RxJS(Reactive Extensions for JavaScript),这是一个用于处理异步操作和事件流的库,为我们处理 HTTP 请求和响应带来了极大的灵活性。

Angular 的 HTTP 客户端模块包含在 @angular/common/http 包中。在使用之前,我们需要在项目中导入这个模块。一般来说,在应用的根模块(通常是 app.module.ts)中进行导入:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform - browser';
import { HttpClientModule } from '@angular/common/http';
import { AppComponent } from './app.component';

@NgModule({
  imports: [
    BrowserModule,
    HttpClientModule
  ],
  declarations: [AppComponent],
  bootstrap: [AppComponent]
})
export class AppModule {}

通过导入 HttpClientModule,我们就可以在应用的组件或服务中使用 HTTP 客户端功能了。

基本的 HTTP 请求

GET 请求

GET 请求是最常见的 HTTP 请求类型,用于从服务器获取数据。在 Angular 中,使用 HttpClientget 方法来发起 GET 请求。假设我们有一个后端 API,地址为 https://example.com/api/users,用于获取用户列表,我们可以这样写:

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class UserService {
  private apiUrl = 'https://example.com/api/users';

  constructor(private http: HttpClient) {}

  getUsers(): Observable<any> {
    return this.http.get(this.apiUrl);
  }
}

在上面的代码中,我们创建了一个 UserService 服务,并在其中定义了一个 getUsers 方法。该方法使用 http.get 方法发起 GET 请求到指定的 API 地址。注意,http.get 方法返回一个 Observable 对象,这是 RxJS 中的核心概念,表示一个可观察的异步操作。我们可以在组件中订阅这个 Observable 来获取实际的数据:

import { Component, OnInit } from '@angular/core';
import { UserService } from './user.service';

@Component({
  selector: 'app - user - list',
  templateUrl: './user - list.component.html',
  styleUrls: ['./user - list.component.css']
})
export class UserListComponent implements OnInit {
  users: any[] = [];

  constructor(private userService: UserService) {}

  ngOnInit(): void {
    this.userService.getUsers().subscribe((data) => {
      this.users = data;
    });
  }
}

UserListComponent 组件的 ngOnInit 生命周期钩子中,我们订阅了 getUsers 方法返回的 Observable。当请求成功时,subscribe 的回调函数会被执行,我们将返回的数据赋值给 users 数组,以便在模板中显示。

POST 请求

POST 请求用于向服务器发送数据,通常用于创建新的资源。假设我们有一个 API 地址 https://example.com/api/users,用于创建新用户,请求体需要包含用户的信息,例如用户名和邮箱。在 Angular 中,使用 HttpClientpost 方法来发起 POST 请求:

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class UserService {
  private apiUrl = 'https://example.com/api/users';

  constructor(private http: HttpClient) {}

  createUser(user: { username: string; email: string }): Observable<any> {
    return this.http.post(this.apiUrl, user);
  }
}

在上面的代码中,createUser 方法接受一个包含 usernameemail 的对象作为参数,并将其作为请求体发送到 API 地址。同样,http.post 方法返回一个 Observable。在组件中调用这个方法:

import { Component } from '@angular/core';
import { UserService } from './user.service';

@Component({
  selector: 'app - create - user',
  templateUrl: './create - user.component.html',
  styleUrls: ['./create - user.component.css']
})
export class CreateUserComponent {
  user = { username: '', email: '' };

  constructor(private userService: UserService) {}

  onSubmit(): void {
    this.userService.createUser(this.user).subscribe((response) => {
      console.log('User created successfully:', response);
    });
  }
}

CreateUserComponent 组件中,我们定义了一个 user 对象来存储用户输入的数据。当用户点击提交按钮(在模板中绑定 onSubmit 方法)时,我们调用 userService.createUser 方法,并在订阅回调中处理服务器的响应。

PUT 请求

PUT 请求通常用于更新服务器上的资源。假设我们有一个 API 地址 https://example.com/api/users/{id},其中 {id} 是用户的唯一标识符,用于更新指定用户的信息。在 Angular 中,使用 HttpClientput 方法来发起 PUT 请求:

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class UserService {
  private apiUrl = 'https://example.com/api/users';

  constructor(private http: HttpClient) {}

  updateUser(id: number, user: { username: string; email: string }): Observable<any> {
    const url = `${this.apiUrl}/${id}`;
    return this.http.put(url, user);
  }
}

updateUser 方法中,我们通过模板字符串构建了完整的 API 地址,并将用户信息作为请求体发送。在组件中调用这个方法:

import { Component } from '@angular/core';
import { UserService } from './user.service';

@Component({
  selector: 'app - update - user',
  templateUrl: './update - user.component.html',
  styleUrls: ['./update - user.component.css']
})
export class UpdateUserComponent {
  user = { id: 1, username: '', email: '' };

  constructor(private userService: UserService) {}

  onSubmit(): void {
    this.userService.updateUser(this.user.id, this.user).subscribe((response) => {
      console.log('User updated successfully:', response);
    });
  }
}

UpdateUserComponent 组件中,我们假设用户已经有了一个 id,并在提交表单时调用 userService.updateUser 方法来更新用户信息。

DELETE 请求

DELETE 请求用于从服务器删除资源。假设我们有一个 API 地址 https://example.com/api/users/{id},用于删除指定用户。在 Angular 中,使用 HttpClientdelete 方法来发起 DELETE 请求:

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class UserService {
  private apiUrl = 'https://example.com/api/users';

  constructor(private http: HttpClient) {}

  deleteUser(id: number): Observable<any> {
    const url = `${this.apiUrl}/${id}`;
    return this.http.delete(url);
  }
}

deleteUser 方法中,我们构建了删除用户的 API 地址,并使用 http.delete 方法发起请求。在组件中调用这个方法:

import { Component } from '@angular/core';
import { UserService } from './user.service';

@Component({
  selector: 'app - delete - user',
  templateUrl: './delete - user.component.html',
  styleUrls: ['./delete - user.component.css']
})
export class DeleteUserComponent {
  userId = 1;

  constructor(private userService: UserService) {}

  onDelete(): void {
    this.userService.deleteUser(this.userId).subscribe(() => {
      console.log('User deleted successfully');
    });
  }
}

DeleteUserComponent 组件中,当用户点击删除按钮(绑定 onDelete 方法)时,我们调用 userService.deleteUser 方法,并在订阅回调中处理删除成功的逻辑。

处理 HTTP 响应

响应数据的类型

Angular 的 HTTP 客户端返回的 Observable 会发出一个包含响应数据的对象。对于大多数常见的请求,响应数据通常是 JSON 格式。在前面的示例中,我们直接使用 any 类型来表示响应数据。然而,为了更好的类型安全性,我们可以定义接口来描述响应数据的结构。例如,对于获取用户列表的响应,我们可以定义如下接口:

export interface User {
  id: number;
  username: string;
  email: string;
}

export interface UserListResponse {
  users: User[];
}

然后在 UserServicegetUsers 方法中,我们可以指定返回的 Observable 的类型:

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { UserListResponse } from './user - list - response.interface';

@Injectable({
  providedIn: 'root'
})
export class UserService {
  private apiUrl = 'https://example.com/api/users';

  constructor(private http: HttpClient) {}

  getUsers(): Observable<UserListResponse> {
    return this.http.get<UserListResponse>(this.apiUrl);
  }
}

这样,在组件中订阅 getUsers 方法返回的 Observable 时,TypeScript 就能更好地进行类型检查:

import { Component, OnInit } from '@angular/core';
import { UserService } from './user.service';
import { UserListResponse } from './user - list - response.interface';

@Component({
  selector: 'app - user - list',
  templateUrl: './user - list.component.html',
  styleUrls: ['./user - list.component.css']
})
export class UserListComponent implements OnInit {
  users: User[] = [];

  constructor(private userService: UserService) {}

  ngOnInit(): void {
    this.userService.getUsers().subscribe((response: UserListResponse) => {
      this.users = response.users;
    });
  }
}

处理响应头

除了响应体中的数据,HTTP 响应还包含响应头信息。在 Angular 中,我们可以通过设置 observe 选项为 'response' 来获取完整的响应对象,包括响应头。例如,对于获取用户列表的请求:

import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders, HttpParams, HttpResponse } from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class UserService {
  private apiUrl = 'https://example.com/api/users';

  constructor(private http: HttpClient) {}

  getUsersWithHeaders(): Observable<HttpResponse<any>> {
    return this.http.get(this.apiUrl, { observe:'response' });
  }
}

在组件中订阅这个方法:

import { Component, OnInit } from '@angular/core';
import { UserService } from './user.service';

@Component({
  selector: 'app - user - list - with - headers',
  templateUrl: './user - list - with - headers.component.html',
  styleUrls: ['./user - list - with - headers.component.css']
})
export class UserListWithHeadersComponent implements OnInit {
  constructor(private userService: UserService) {}

  ngOnInit(): void {
    this.userService.getUsersWithHeaders().subscribe((response) => {
      console.log('Response headers:', response.headers);
      console.log('Response body:', response.body);
    });
  }
}

在上面的代码中,HttpResponse 类型的响应对象包含 headers 属性,我们可以从中获取响应头信息。

处理 HTTP 请求错误

在与后端 API 交互过程中,难免会遇到各种错误,如网络故障、服务器错误等。Angular 的 HTTP 客户端提供了完善的错误处理机制。当 HTTP 请求失败时,Observable 会发出一个错误通知。我们可以在 subscribe 方法中通过第二个参数来处理错误。例如,对于获取用户列表的请求:

import { Component, OnInit } from '@angular/core';
import { UserService } from './user.service';

@Component({
  selector: 'app - user - list - with - error - handling',
  templateUrl: './user - list - with - error - handling.component.html',
  styleUrls: ['./user - list - with - error - handling.component.css']
})
export class UserListWithErrorHandlingComponent implements OnInit {
  users: any[] = [];

  constructor(private userService: UserService) {}

  ngOnInit(): void {
    this.userService.getUsers().subscribe(
      (data) => {
        this.users = data;
      },
      (error) => {
        console.error('Error fetching users:', error);
      }
    );
  }
}

在上面的代码中,如果请求失败,subscribe 的第二个回调函数会被执行,我们可以在其中记录错误信息或向用户显示友好的错误提示。

不同类型的错误

HTTP 客户端可能遇到多种类型的错误,常见的有:

  • 网络错误:例如网络连接中断,这种情况下 error 对象的 name 属性通常为 'HttpErrorResponse',并且 status 属性为 0,表示没有从服务器收到有效的响应。
this.userService.getUsers().subscribe(
  (data) => {
    this.users = data;
  },
  (error) => {
    if (error.name === 'HttpErrorResponse' && error.status === 0) {
      console.error('Network error. Please check your connection.');
    }
  }
);
  • HTTP 状态码错误:当服务器返回的 HTTP 状态码表示错误时,如 404(未找到)、500(服务器内部错误)等。error 对象的 status 属性会包含具体的状态码。
this.userService.getUsers().subscribe(
  (data) => {
    this.users = data;
  },
  (error) => {
    if (error.status === 404) {
      console.error('The requested resource was not found.');
    } else if (error.status === 500) {
      console.error('Server encountered an internal error.');
    }
  }
);

配置 HTTP 请求

设置请求头

在很多情况下,我们需要在 HTTP 请求中设置请求头,例如设置 Content - Typeapplication/json 以告知服务器请求体是 JSON 格式的数据,或者设置认证令牌(如 JWT)。在 Angular 中,使用 HttpHeaders 类来设置请求头。例如,对于创建用户的 POST 请求,设置 Content - Type 头:

import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class UserService {
  private apiUrl = 'https://example.com/api/users';

  constructor(private http: HttpClient) {}

  createUser(user: { username: string; email: string }): Observable<any> {
    const headers = new HttpHeaders({
      'Content - Type': 'application/json'
    });
    return this.http.post(this.apiUrl, user, { headers });
  }
}

在上面的代码中,我们创建了一个 HttpHeaders 对象,并设置了 Content - Type 头。然后将这个 headers 对象作为第三个参数传递给 http.post 方法。

如果需要设置多个请求头,可以链式调用 set 方法:

const headers = new HttpHeaders()
 .set('Content - Type', 'application/json')
 .set('Authorization', 'Bearer your - token - here');

设置查询参数

有时候我们需要在 URL 中添加查询参数,例如对获取用户列表的请求进行分页或过滤。在 Angular 中,使用 HttpParams 类来设置查询参数。假设我们有一个 API 支持通过 pagelimit 参数进行分页,我们可以这样设置查询参数:

import { Injectable } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class UserService {
  private apiUrl = 'https://example.com/api/users';

  constructor(private http: HttpClient) {}

  getUsers(page: number, limit: number): Observable<any> {
    let params = new HttpParams()
     .set('page', page.toString())
     .set('limit', limit.toString());
    return this.http.get(this.apiUrl, { params });
  }
}

在上面的代码中,我们创建了一个 HttpParams 对象,并通过 set 方法设置了 pagelimit 参数。然后将这个 params 对象作为第三个参数传递给 http.get 方法。这样,请求的 URL 会变成类似 https://example.com/api/users?page=1&limit=10 的形式。

拦截器的使用

拦截器的概念

Angular 的 HTTP 拦截器是一种强大的机制,它允许我们在 HTTP 请求发送前和响应接收后对请求和响应进行拦截和处理。拦截器可以用于多种场景,例如添加全局的请求头(如认证令牌)、处理错误统一化、记录请求和响应日志等。

创建拦截器

要创建一个 HTTP 拦截器,我们需要创建一个实现 HttpInterceptor 接口的类。例如,创建一个用于添加认证令牌到请求头的拦截器:

import { Injectable } from '@angular/core';
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  constructor() {}

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const token = localStorage.getItem('token');
    let authReq = req;
    if (token) {
      authReq = req.clone({
        headers: req.headers.set('Authorization', `Bearer ${token}`)
      });
    }
    return next.handle(authReq);
  }
}

在上面的代码中,AuthInterceptor 类实现了 HttpInterceptor 接口的 intercept 方法。在 intercept 方法中,我们首先从 localStorage 中获取认证令牌。如果令牌存在,我们通过 req.clone 方法创建一个新的请求对象,并在请求头中添加 Authorization 头。然后我们调用 next.handle(authReq) 将请求传递给下一个拦截器(如果有)或最终发送到服务器。

注册拦截器

创建好拦截器后,我们需要在应用模块中注册它。在 app.module.ts 中:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform - browser';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { AppComponent } from './app.component';
import { AuthInterceptor } from './auth - interceptor';

@NgModule({
  imports: [
    BrowserModule,
    HttpClientModule
  ],
  declarations: [AppComponent],
  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: AuthInterceptor,
      multi: true
    }
  ],
  bootstrap: [AppComponent]
})
export class AppModule {}

providers 数组中,我们通过 provide 指定要提供的是 HTTP_INTERCEPTORSuseClass 指定使用我们创建的 AuthInterceptor 类,multi: true 表示可以有多个拦截器。

多个拦截器的执行顺序

当应用中有多个拦截器时,它们会按照注册的顺序依次执行。先注册的拦截器先处理请求,后注册的拦截器后处理请求。在响应阶段,顺序则相反,后注册的拦截器先处理响应,先注册的拦截器后处理响应。例如,我们有一个日志拦截器和前面的认证拦截器:

import { Injectable } from '@angular/core';
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';

@Injectable()
export class LoggingInterceptor implements HttpInterceptor {
  constructor() {}

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    console.log('Request started:', req.url);
    return next.handle(req).pipe(
      tap(() => {
        console.log('Request completed:', req.url);
      })
    );
  }
}

app.module.ts 中注册这两个拦截器:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform - browser';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { AppComponent } from './app.component';
import { AuthInterceptor } from './auth - interceptor';
import { LoggingInterceptor } from './logging - interceptor';

@NgModule({
  imports: [
    BrowserModule,
    HttpClientModule
  ],
  declarations: [AppComponent],
  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: LoggingInterceptor,
      multi: true
    },
    {
      provide: HTTP_INTERCEPTORS,
      useClass: AuthInterceptor,
      multi: true
    }
  ],
  bootstrap: [AppComponent]
})
export class AppModule {}

在这个例子中,LoggingInterceptor 先处理请求,会在控制台打印请求开始的信息。然后 AuthInterceptor 处理请求,添加认证头。在响应阶段,AuthInterceptor 先处理响应,然后 LoggingInterceptor 处理响应,并在控制台打印请求完成的信息。

高级主题:与 RxJS 结合使用

使用 RxJS 操作符处理 HTTP 响应

由于 Angular 的 HTTP 客户端基于 RxJS,我们可以使用 RxJS 的各种操作符来处理 HTTP 请求返回的 Observable。例如,map 操作符可以用于转换响应数据。假设我们获取的用户列表响应数据中每个用户对象包含一个 created_at 字段,我们想将其转换为 JavaScript 的 Date 对象:

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

@Injectable({
  providedIn: 'root'
})
export class UserService {
  private apiUrl = 'https://example.com/api/users';

  constructor(private http: HttpClient) {}

  getUsers(): Observable<any> {
    return this.http.get(this.apiUrl).pipe(
      map((data: any) => {
        return data.users.map((user: any) => {
          user.created_at = new Date(user.created_at);
          return user;
        });
      })
    );
  }
}

在上面的代码中,我们使用 map 操作符对 http.get 返回的 Observable 进行处理。map 操作符接收一个回调函数,该回调函数接收响应数据,并返回转换后的数据。

合并多个 HTTP 请求

有时候我们需要同时发起多个 HTTP 请求,并在所有请求都完成后进行一些操作。RxJS 提供了 forkJoin 操作符来实现这个功能。假设我们有一个服务,需要同时获取用户列表和用户总数:

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { forkJoin } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class UserService {
  private usersUrl = 'https://example.com/api/users';
  private totalUsersUrl = 'https://example.com/api/users/count';

  constructor(private http: HttpClient) {}

  getUsersAndTotal(): Observable<{ users: any[]; total: number }> {
    const usersObservable = this.http.get(this.usersUrl);
    const totalObservable = this.http.get<{ count: number }>(this.totalUsersUrl);

    return forkJoin({
      users: usersObservable,
      total: totalObservable.pipe(
        map((response) => response.count)
      )
    });
  }
}

在上面的代码中,我们分别创建了获取用户列表和用户总数的 Observable。然后使用 forkJoin 操作符将这两个 Observable 合并成一个新的 ObservableforkJoin 会等待所有传入的 Observable 都发出值后,将这些值合并成一个对象(这里是 { users: any[], total: number })并发出。在组件中订阅这个方法:

import { Component, OnInit } from '@angular/core';
import { UserService } from './user.service';

@Component({
  selector: 'app - user - stats',
  templateUrl: './user - stats.component.html',
  styleUrls: ['./user - stats.component.css']
})
export class UserStatsComponent implements OnInit {
  users: any[] = [];
  total: number = 0;

  constructor(private userService: UserService) {}

  ngOnInit(): void {
    this.userService.getUsersAndTotal().subscribe((data) => {
      this.users = data.users;
      this.total = data.total;
    });
  }
}

这样,我们就可以在所有请求完成后,同时获取用户列表和用户总数,并进行相应的处理。

通过以上对 Angular HTTP 客户端与后端 API 交互的详细介绍,你应该对如何在 Angular 应用中进行高效的 HTTP 通信有了全面的了解。无论是基本的请求类型、响应处理、错误处理,还是配置请求、使用拦截器以及与 RxJS 结合使用等方面,都为构建健壮的前端与后端交互提供了有力的工具和方法。