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

利用HttpClient模块实现Angular数据获取

2022-01-124.7k 阅读

Angular 中的 HttpClient 模块简介

在 Angular 开发中,与后端服务器进行数据交互是一项极为常见且关键的任务。HttpClient 模块便是 Angular 提供的用于执行 HTTP 请求的核心工具,它取代了之前的 Http 模块,为开发者带来了诸多显著的优势。

HttpClient 模块基于 RxJS(Reactive Extensions for JavaScript)构建,这意味着它能够很好地与 Angular 的响应式编程模型相融合。RxJS 提供了强大的操作符来处理异步操作,如数据的获取、转换和处理。使用 HttpClient 模块,开发者可以轻松地将 HTTP 请求的结果转换为可观察对象(Observable),进而利用 RxJS 的丰富功能进行操作。

HttpClient 模块相较于旧的 Http 模块,有着更高的可读性和可维护性。它的 API 设计更加简洁直观,方法命名更加符合语义,使得代码更易于理解和编写。同时,HttpClient 模块还提供了更好的错误处理机制,能够帮助开发者更方便地捕获和处理在数据获取过程中可能出现的各种错误。

安装与导入 HttpClient 模块

在开始使用 HttpClient 模块之前,需要确保它已经被正确安装和导入到 Angular 项目中。

在 Angular CLI 创建的项目中,HttpClient 模块通常已经作为依赖项包含在内。如果项目是手动搭建的,可能需要通过 npm 来安装 @angular/common - http 包。可以在项目的根目录下执行以下命令进行安装:

npm install @angular/common - http

安装完成后,需要在 Angular 应用的模块中导入 HttpClientModule。通常在 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({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    HttpClientModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule {}

通过上述步骤,HttpClientModule 就被成功导入到应用模块中,可以在整个应用中使用它来执行 HTTP 请求了。

基本的 GET 请求

GET 请求是最常用的 HTTP 请求方法之一,用于从服务器获取数据。在 Angular 中,使用 HttpClient 模块执行 GET 请求非常简单。

假设后端有一个 API 端点 https://example.com/api/data,它返回一些 JSON 格式的数据。可以在服务类中编写如下代码来执行 GET 请求:

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

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

  getData(): Observable<any> {
    return this.http.get('https://example.com/api/data');
  }
}

在上述代码中:

  1. 首先通过依赖注入将 HttpClient 实例注入到 DataService 服务类的构造函数中。
  2. 然后定义了一个 getData 方法,该方法使用 http.get 方法发起 GET 请求,请求的 URL 为 https://example.com/api/datahttp.get 方法返回一个 Observable 对象,这里的 Observable 会在 HTTP 请求成功完成后发出后端返回的数据。

在组件中使用这个服务来获取数据:

import { Component, OnInit } from '@angular/core';
import { DataService } from './data.service';

@Component({
  selector: 'app - my - component',
  templateUrl: './my - component.html',
  styleUrls: ['./my - component.css']
})
export class MyComponent implements OnInit {
  data: any;

  constructor(private dataService: DataService) {}

  ngOnInit(): void {
    this.dataService.getData().subscribe((response) => {
      this.data = response;
      console.log(this.data);
    });
  }
}

MyComponent 组件中:

  1. 通过依赖注入将 DataService 注入到组件的构造函数中。
  2. ngOnInit 生命周期钩子函数中调用 dataService.getData() 方法获取数据。
  3. 使用 subscribe 方法订阅 Observable,当 Observable 发出数据(即 HTTP 请求成功返回数据)时,将返回的数据赋值给组件的 data 属性,并在控制台打印出来。

GET 请求带参数

很多时候,需要在 GET 请求中携带参数。例如,后端 API 可能根据传递的参数来返回不同的数据。假设后端 API 为 https://example.com/api/search,它接受一个 query 参数用于搜索数据。

在服务类中可以这样编写代码:

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

@Injectable({
  providedIn: 'root'
})
export class SearchService {
  constructor(private http: HttpClient) {}

  searchData(query: string): Observable<any> {
    let params = new HttpParams();
    params = params.append('query', query);

    return this.http.get('https://example.com/api/search', { params });
  }
}

在上述代码中:

  1. 导入了 HttpParams 类,用于构建请求参数。
  2. searchData 方法中,首先创建一个 HttpParams 实例,然后使用 append 方法添加 query 参数,值为传入的 query 字符串。
  3. http.get 方法中,通过第二个参数 { params } 将构建好的参数传递给请求。

在组件中使用这个服务:

import { Component, OnInit } from '@angular/core';
import { SearchService } from './search.service';

@Component({
  selector: 'app - search - component',
  templateUrl: './search - component.html',
  styleUrls: ['./search - component.css']
})
export class SearchComponent implements OnInit {
  searchQuery = '';
  searchResults: any;

  constructor(private searchService: SearchService) {}

  ngOnInit(): void {}

  onSearch(): void {
    this.searchService.searchData(this.searchQuery).subscribe((response) => {
      this.searchResults = response;
      console.log(this.searchResults);
    });
  }
}

SearchComponent 组件中:

  1. 定义了 searchQuery 用于存储用户输入的搜索关键词,searchResults 用于存储搜索结果。
  2. onSearch 方法中,调用 searchService.searchData 方法并传入 searchQuery,订阅返回的 Observable 以处理搜索结果。

处理响应数据类型

默认情况下,http.get 方法返回的 Observable 发出的数据类型是 any。然而,在实际开发中,为了提高代码的类型安全性,可以指定返回数据的具体类型。

假设后端返回的数据结构如下:

{
  "id": 1,
  "name": "John Doe",
  "email": "johndoe@example.com"
}

可以定义一个接口来描述这个数据结构:

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

然后在服务类中修改 getData 方法,指定返回数据的类型:

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common - http';
import { Observable } from 'rxjs';
import { User } from './user.model';

@Injectable({
  providedIn: 'root'
})
export class UserService {
  constructor(private http: HttpClient) {}

  getUser(): Observable<User> {
    return this.http.get<User>('https://example.com/api/user');
  }
}

在组件中使用时,subscribe 回调函数中的参数类型也会被正确推断:

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

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

  constructor(private userService: UserService) {}

  ngOnInit(): void {
    this.userService.getUser().subscribe((response: User) => {
      this.user = response;
      console.log(this.user.name);
    });
  }
}

通过这种方式,TypeScript 可以在编译时检查类型错误,提高代码的健壮性。

错误处理

在数据获取过程中,可能会遇到各种错误,如网络故障、服务器错误等。HttpClient 模块提供了良好的错误处理机制。

当 HTTP 请求失败时,Observable 会发出一个错误通知。可以在 subscribe 方法中通过第二个参数来捕获错误:

import { Component, OnInit } from '@angular/core';
import { DataService } from './data.service';

@Component({
  selector: 'app - error - component',
  templateUrl: './error - component.html',
  styleUrls: ['./error - component.css']
})
export class ErrorComponent implements OnInit {
  data: any;

  constructor(private dataService: DataService) {}

  ngOnInit(): void {
    this.dataService.getData().subscribe(
      (response) => {
        this.data = response;
        console.log(this.data);
      },
      (error) => {
        console.error('An error occurred:', error);
      }
    );
  }
}

在上述代码中,当 getData 方法发起的请求失败时,subscribe 的第二个回调函数会被执行,错误信息会被打印到控制台。

除了在 subscribe 中处理错误,还可以使用 RxJS 的 catchError 操作符来全局处理错误。在服务类中可以这样使用:

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

@Injectable({
  providedIn: 'root'
})
export class ErrorHandlingService {
  constructor(private http: HttpClient) {}

  getData(): Observable<any> {
    return this.http.get('https://example.com/api/data').pipe(
      catchError((error) => {
        console.error('Global error handling:', error);
        throw error;
      })
    );
  }
}

ErrorHandlingService 中,通过 pipe 方法使用 catchError 操作符。在 catchError 的回调函数中,先打印错误信息,然后重新抛出错误,这样在组件中仍然可以通过 subscribe 的错误回调来处理错误,同时也实现了全局的错误日志记录。

POST 请求

POST 请求通常用于向服务器提交数据,比如用户注册、表单提交等场景。假设后端有一个 API 端点 https://example.com/api/register,用于接收用户注册信息。

首先定义一个接口来描述注册数据的结构:

export interface RegisterData {
  username: string;
  password: string;
}

然后在服务类中编写 POST 请求的代码:

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common - http';
import { Observable } from 'rxjs';
import { RegisterData } from './register.model';

@Injectable({
  providedIn: 'root'
})
export class RegisterService {
  constructor(private http: HttpClient) {}

  registerUser(data: RegisterData): Observable<any> {
    return this.http.post('https://example.com/api/register', data);
  }
}

在上述代码中:

  1. registerUser 方法接受一个 RegisterData 类型的参数 data,该参数包含用户注册的信息。
  2. 使用 http.post 方法发起 POST 请求,第一个参数是请求的 URL,第二个参数是要提交的数据。

在组件中使用这个服务:

import { Component, OnInit } from '@angular/core';
import { RegisterService } from './register.service';
import { RegisterData } from './register.model';

@Component({
  selector: 'app - register - component',
  templateUrl: './register - component.html',
  styleUrls: ['./register - component.css']
})
export class RegisterComponent implements OnInit {
  registerData: RegisterData = {
    username: '',
    password: ''
  };

  constructor(private registerService: RegisterService) {}

  ngOnInit(): void {}

  onRegister(): void {
    this.registerService.registerUser(this.registerData).subscribe((response) => {
      console.log('Registration successful:', response);
    });
  }
}

RegisterComponent 组件中:

  1. 定义了 registerData 用于存储用户输入的注册信息。
  2. onRegister 方法中,调用 registerService.registerUser 方法并传入 registerData,订阅返回的 Observable 以处理注册结果。

PUT 请求

PUT 请求一般用于更新服务器上的资源。假设后端有一个 API 端点 https://example.com/api/users/{id},用于更新用户信息,其中 {id} 是用户的唯一标识。

定义一个接口来描述用户更新数据的结构:

export interface UserUpdate {
  name?: string;
  email?: string;
}

在服务类中编写 PUT 请求的代码:

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common - http';
import { Observable } from 'rxjs';
import { UserUpdate } from './user - update.model';

@Injectable({
  providedIn: 'root'
})
export class UserUpdateService {
  constructor(private http: HttpClient) {}

  updateUser(id: number, data: UserUpdate): Observable<any> {
    const url = `https://example.com/api/users/${id}`;
    return this.http.put(url, data);
  }
}

在上述代码中:

  1. updateUser 方法接受两个参数,id 用于指定要更新的用户的标识,dataUserUpdate 类型的对象,包含要更新的用户信息。
  2. 使用模板字符串构建请求的 URL,然后使用 http.put 方法发起 PUT 请求,传入 URL 和更新数据。

在组件中使用这个服务:

import { Component, OnInit } from '@angular/core';
import { UserUpdateService } from './user - update.service';
import { UserUpdate } from './user - update.model';

@Component({
  selector: 'app - user - update - component',
  templateUrl: './user - update - component.html',
  styleUrls: ['./user - update - component.css']
})
export class UserUpdateComponent implements OnInit {
  userId = 1;
  userUpdateData: UserUpdate = {
    name: 'Updated Name'
  };

  constructor(private userUpdateService: UserUpdateService) {}

  ngOnInit(): void {}

  onUpdate(): void {
    this.userUpdateService.updateUser(this.userId, this.userUpdateData).subscribe((response) => {
      console.log('User updated successfully:', response);
    });
  }
}

UserUpdateComponent 组件中:

  1. 定义了 userIduserUpdateData,分别表示要更新的用户的 ID 和更新的数据。
  2. onUpdate 方法中,调用 userUpdateService.updateUser 方法并传入 userIduserUpdateData,订阅返回的 Observable 以处理更新结果。

DELETE 请求

DELETE 请求用于从服务器删除资源。假设后端有一个 API 端点 https://example.com/api/users/{id},用于删除指定 ID 的用户。

在服务类中编写 DELETE 请求的代码:

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

@Injectable({
  providedIn: 'root'
})
export class UserDeleteService {
  constructor(private http: HttpClient) {}

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

在上述代码中:

  1. deleteUser 方法接受一个参数 id,用于指定要删除的用户的 ID。
  2. 使用模板字符串构建请求的 URL,然后使用 http.delete 方法发起 DELETE 请求。

在组件中使用这个服务:

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

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

  constructor(private userDeleteService: UserDeleteService) {}

  ngOnInit(): void {}

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

UserDeleteComponent 组件中:

  1. 定义了 userId 表示要删除的用户的 ID。
  2. onDelete 方法中,调用 userDeleteService.deleteUser 方法并传入 userId,订阅返回的 Observable 以处理删除结果。

设置请求头

在一些情况下,需要在 HTTP 请求中设置自定义的请求头。例如,后端 API 可能要求在请求中包含认证令牌(token)。

假设要在请求头中添加一个 Authorization 头,值为 Bearer <token>。在服务类中可以这样设置:

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

@Injectable({
  providedIn: 'root'
})
export class AuthenticatedService {
  constructor(private http: HttpClient) {}

  getAuthenticatedData(): Observable<any> {
    const token = 'your - token - here';
    const headers = new HttpHeaders().set('Authorization', `Bearer ${token}`);

    return this.http.get('https://example.com/api/authenticated - data', { headers });
  }
}

在上述代码中:

  1. 导入了 HttpHeaders 类。
  2. 创建一个 HttpHeaders 实例,使用 set 方法添加 Authorization 头。
  3. http.get 方法中,通过第二个参数 { headers } 将设置好的请求头传递给请求。

拦截器的使用

拦截器是 Angular 中非常强大的功能,它允许开发者在 HTTP 请求发送前和响应返回后对请求和响应进行统一的处理。例如,可以使用拦截器来统一添加请求头、处理错误、记录日志等。

首先创建一个拦截器类,实现 HttpInterceptor 接口:

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

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

  intercept(
    request: HttpRequest<any>,
    next: HttpHandler
  ): Observable<HttpEvent<any>> {
    const token = 'your - token - here';
    const modifiedRequest = request.clone({
      headers: request.headers.set('Authorization', `Bearer ${token}`)
    });

    return next.handle(modifiedRequest);
  }
}

在上述代码中:

  1. AuthInterceptorService 类实现了 HttpInterceptor 接口,必须实现 intercept 方法。
  2. intercept 方法中,首先获取认证令牌 token,然后使用 request.clone 方法创建一个新的请求对象 modifiedRequest,并在新请求对象的头信息中添加 Authorization 头。
  3. 最后通过 next.handle(modifiedRequest) 将修改后的请求传递给下一个处理器。

要使拦截器生效,需要在应用模块的 providers 数组中注册拦截器:

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 { AuthInterceptorService } from './auth - interceptor.service';

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

在上述 AppModule 中,通过 providers 数组将 AuthInterceptorService 注册为 HTTP_INTERCEPTORS 提供器。multi: true 表示可以有多个拦截器,它们会按照注册的顺序依次执行。

并发请求

在某些场景下,可能需要同时发起多个 HTTP 请求,并在所有请求都完成后进行处理。可以使用 RxJS 的 forkJoin 操作符来实现并发请求。

假设需要同时获取用户信息和用户的订单列表,后端 API 分别为 https://example.com/api/userhttps://example.com/api/orders/{userId}

在服务类中编写获取用户信息和订单列表的方法:

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

@Injectable({
  providedIn: 'root'
})
export class ConcurrentService {
  constructor(private http: HttpClient) {}

  getUser(): Observable<any> {
    return this.http.get('https://example.com/api/user');
  }

  getOrders(userId: number): Observable<any> {
    const url = `https://example.com/api/orders/${userId}`;
    return this.http.get(url);
  }
}

在组件中使用 forkJoin 来并发执行这两个请求:

import { Component, OnInit } from '@angular/core';
import { ConcurrentService } from './concurrent.service';
import { forkJoin } from 'rxjs';

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

  constructor(private concurrentService: ConcurrentService) {}

  ngOnInit(): void {
    const userObservable = this.concurrentService.getUser();
    const ordersObservable = this.concurrentService.getOrders(1);

    forkJoin([userObservable, ordersObservable]).subscribe(([userResponse, ordersResponse]) => {
      this.user = userResponse;
      this.orders = ordersResponse;
      console.log('User:', this.user);
      console.log('Orders:', this.orders);
    });
  }
}

在上述代码中:

  1. 分别获取 getUsergetOrders 方法返回的 Observable
  2. 使用 forkJoin 操作符将这两个 Observable 组合起来,forkJoin 会等待所有的 Observable 都发出数据后,将它们的数据作为数组传递给 subscribe 的回调函数。
  3. subscribe 的回调函数中,解构数组得到用户信息和订单列表,并进行相应的处理。

缓存数据

在前端开发中,为了提高性能,减少不必要的 HTTP 请求,可以对获取的数据进行缓存。可以使用 RxJS 的 shareReplay 操作符来实现简单的数据缓存。

假设在服务类中有一个获取配置数据的方法:

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

@Injectable({
  providedIn: 'root'
})
export class ConfigService {
  private configObservable: Observable<any>;

  constructor(private http: HttpClient) {}

  getConfig(): Observable<any> {
    if (!this.configObservable) {
      this.configObservable = this.http.get('https://example.com/api/config').pipe(
        shareReplay(1)
      );
    }

    return this.configObservable;
  }
}

在上述代码中:

  1. 定义了一个私有属性 configObservable 用于存储配置数据的 Observable
  2. getConfig 方法中,首先检查 configObservable 是否已经存在,如果不存在,则发起 HTTP 请求获取配置数据,并使用 shareReplay(1) 操作符对 Observable 进行处理。shareReplay(1) 会缓存最后发出的值,并将其重新发送给新的订阅者,这样后续的订阅者就不会再次发起 HTTP 请求,而是直接使用缓存的值。
  3. 返回 configObservable

在组件中使用这个服务:

import { Component, OnInit } from '@angular/core';
import { ConfigService } from './config.service';

@Component({
  selector: 'app - config - component',
  templateUrl: './config - component.html',
  styleUrls: ['./config - component.css']
})
export class ConfigComponent implements OnInit {
  config: any;

  constructor(private configService: ConfigService) {}

  ngOnInit(): void {
    this.configService.getConfig().subscribe((response) => {
      this.config = response;
      console.log(this.config);
    });
  }
}

ConfigComponent 组件中,多次调用 configService.getConfig() 方法,只会发起一次 HTTP 请求,后续调用会直接使用缓存的数据。

通过以上对 Angular 中 HttpClient 模块的详细介绍,包括基本请求、参数处理、错误处理、各种请求方法、请求头设置、拦截器、并发请求以及数据缓存等方面,开发者可以全面掌握利用 HttpClient 模块实现高效的数据获取与交互,从而构建出更加健壮和高性能的 Angular 应用程序。