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

HttpClient模块的Angular请求体构建与发送

2022-03-277.4k 阅读

1. 前置知识:Angular 与 HttpClient 模块简介

在深入探讨 HttpClient 模块的请求体构建与发送之前,先来简单回顾一下 Angular 以及 HttpClient 模块的基本概念。

Angular 是一款由 Google 维护的开源 JavaScript 框架,用于构建客户端单页应用程序(SPA)。它提供了一套完整的开发架构,涵盖了从组件化开发、数据绑定、路由管理到依赖注入等一系列功能,大大提高了前端开发的效率和代码的可维护性。

HttpClient 模块是 Angular 中用于处理 HTTP 请求的核心模块。它取代了之前的 Http 模块,提供了更现代化、功能更强大的 API 来与后端服务器进行通信。HttpClient 模块基于 Observables 构建,这使得我们可以方便地处理异步操作、错误处理以及对请求和响应进行链式处理。

要在 Angular 项目中使用 HttpClient 模块,首先需要在 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 {}

通过上述配置,我们就可以在整个应用中使用 HttpClient 模块来发送 HTTP 请求了。

2. 构建请求体基础

2.1 JSON 格式请求体

在现代的 Web 开发中,JSON(JavaScript Object Notation)是最常用的数据交换格式之一。当我们向后端服务器发送请求时,很多时候需要将数据以 JSON 格式放在请求体中。

假设我们有一个简单的用户注册接口,需要传递用户名和密码。首先,在 Angular 组件中定义一个包含这些数据的对象,然后使用 JSON.stringify() 方法将其转换为 JSON 字符串作为请求体。

import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http';

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

  constructor(private http: HttpClient) {}

  register() {
    const requestBody = JSON.stringify(this.user);
    this.http.post('http://example.com/api/register', requestBody, {
      headers: {
        'Content - Type': 'application/json'
      }
    }).subscribe(response => {
      console.log('Registration successful:', response);
    }, error => {
      console.error('Registration failed:', error);
    });
  }
}

在上述代码中,register 方法首先将 user 对象转换为 JSON 字符串作为请求体,然后通过 http.post 方法发送 POST 请求。注意,我们设置了 Content - Type 头为 application/json,以告知服务器请求体的数据格式为 JSON。

2.2 URL 编码格式请求体

虽然 JSON 格式非常流行,但有些后端服务可能期望请求体是 URL 编码格式的数据。URL 编码格式通常用于传统的表单提交场景。

在 Angular 中,我们可以使用 URLSearchParams 类来构建 URL 编码格式的请求体。以下是一个示例,假设我们有一个登录接口,需要传递用户名和密码:

import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Component({
  selector: 'app - login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.css']
})
export class LoginComponent {
  username = '';
  password = '';

  constructor(private http: HttpClient) {}

  login() {
    const params = new URLSearchParams();
    params.append('username', this.username);
    params.append('password', this.password);

    this.http.post('http://example.com/api/login', params.toString(), {
      headers: {
        'Content - Type': 'application/x - www - form - urlencoded'
      }
    }).subscribe(response => {
      console.log('Login successful:', response);
    }, error => {
      console.error('Login failed:', error);
    });
  }
}

在上述代码中,URLSearchParams 类的 append 方法用于添加键值对,最后通过 toString 方法将其转换为 URL 编码格式的字符串作为请求体。同样,我们设置了 Content - Type 头为 application/x - www - form - urlencoded

3. 复杂请求体构建

3.1 包含嵌套对象和数组的 JSON 请求体

实际应用中,请求体可能包含复杂的嵌套对象和数组结构。例如,我们有一个创建订单的接口,订单中包含客户信息、订单明细(多个商品)等。

import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Component({
  selector: 'app - create - order',
  templateUrl: './create - order.component.html',
  styleUrls: ['./create - order.component.css']
})
export class CreateOrderComponent {
  order = {
    customer: {
      name: '',
      email: '',
      phone: ''
    },
    orderItems: []
  };

  constructor(private http: HttpClient) {}

  addItem(product: string, quantity: number) {
    this.order.orderItems.push({ product, quantity });
  }

  createOrder() {
    const requestBody = JSON.stringify(this.order);
    this.http.post('http://example.com/api/create - order', requestBody, {
      headers: {
        'Content - Type': 'application/json'
      }
    }).subscribe(response => {
      console.log('Order created successfully:', response);
    }, error => {
      console.error('Order creation failed:', error);
    });
  }
}

在上述代码中,order 对象包含一个 customer 嵌套对象和一个 orderItems 数组。addItem 方法用于向 orderItems 数组中添加商品信息。createOrder 方法将整个 order 对象转换为 JSON 格式的请求体并发送 POST 请求。

3.2 多部分表单数据请求体(Multipart Form Data)

多部分表单数据常用于上传文件等场景。在 Angular 中,我们可以使用 FormData 对象来构建多部分表单数据请求体。

假设我们有一个上传文件的接口,同时还需要传递一些额外的文本信息,例如文件名描述:

import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Component({
  selector: 'app - file - upload',
  templateUrl: './file - upload.component.html',
  styleUrls: ['./file - upload.component.css']
})
export class FileUploadComponent {
  file: File | null = null;
  description = '';

  constructor(private http: HttpClient) {}

  onFileChange(event: any) {
    this.file = event.target.files[0];
  }

  uploadFile() {
    const formData = new FormData();
    if (this.file) {
      formData.append('file', this.file, this.file.name);
    }
    formData.append('description', this.description);

    this.http.post('http://example.com/api/upload - file', formData).subscribe(response => {
      console.log('File uploaded successfully:', response);
    }, error => {
      console.error('File upload failed:', error);
    });
  }
}

在上述代码中,FormData 对象的 append 方法用于添加文件和文本字段。注意,当上传文件时,append 方法的第一个参数是后端接口期望的字段名,第二个参数是 File 对象,第三个参数是文件名。这里不需要手动设置 Content - Type 头,因为 FormData 会自动设置为 multipart/form - data

4. 发送请求的不同方法及参数详解

4.1 GET 请求

GET 请求通常用于从服务器获取数据,请求参数一般通过 URL 的查询字符串传递。在 HttpClient 模块中,使用 http.get 方法发送 GET 请求。

import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http';

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

  constructor(private http: HttpClient) {}

  fetchData() {
    this.http.get('http://example.com/api/data').subscribe(response => {
      this.data = response;
      console.log('Data fetched successfully:', this.data);
    }, error => {
      console.error('Data fetch failed:', error);
    });
  }
}

如果需要传递查询参数,可以使用 HttpParams 类。例如,假设我们有一个搜索接口,需要传递搜索关键词:

import { Component } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';

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

  constructor(private http: HttpClient) {}

  search() {
    let params = new HttpParams();
    params = params.set('q', this.searchTerm);

    this.http.get('http://example.com/api/search', { params }).subscribe(response => {
      this.results = response;
      console.log('Search results:', this.results);
    }, error => {
      console.error('Search failed:', error);
    });
  }
}

在上述代码中,HttpParams 类的 set 方法用于设置查询参数,然后通过 { params } 选项将参数传递给 http.get 方法。

4.2 POST 请求

POST 请求常用于向服务器提交数据,数据通常放在请求体中。前面已经介绍了构建不同格式请求体并发送 POST 请求的示例。这里再详细介绍一下 http.post 方法的其他参数。

除了请求 URL 和请求体,http.post 方法还接受一个可选的配置对象,用于设置请求头、响应类型等。例如:

import { Component } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';

@Component({
  selector: 'app - custom - post',
  templateUrl: './custom - post.component.html',
  styleUrls: ['./custom - post.component.css']
})
export class CustomPostComponent {
  data = {
    message: 'Hello, server!'
  };

  constructor(private http: HttpClient) {}

  sendCustomPost() {
    const headers = new HttpHeaders({
      'Authorization': 'Bearer your - token - here',
      'Content - Type': 'application/json'
    });

    this.http.post('http://example.com/api/custom - post', this.data, { headers, responseType: 'text' }).subscribe(response => {
      console.log('Custom POST response:', response);
    }, error => {
      console.error('Custom POST failed:', error);
    });
  }
}

在上述代码中,我们通过 HttpHeaders 类设置了 AuthorizationContent - Type 头,同时通过 responseType 选项指定响应类型为 text

4.3 PUT 请求

PUT 请求通常用于更新服务器上的资源。它的使用方式与 POST 请求类似,只是语义不同。

import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Component({
  selector: 'app - update - data',
  templateUrl: './update - data.component.html',
  styleUrls: ['./update - data.component.css']
})
export class UpdateDataComponent {
  updatedData = {
    id: 1,
    name: 'Updated Name'
  };

  constructor(private http: HttpClient) {}

  update() {
    this.http.put('http://example.com/api/update - data', this.updatedData).subscribe(response => {
      console.log('Data updated successfully:', response);
    }, error => {
      console.error('Data update failed:', error);
    });
  }
}

在上述代码中,我们通过 http.put 方法将 updatedData 发送到服务器,以更新指定资源。

4.4 DELETE 请求

DELETE 请求用于从服务器删除资源。

import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http';

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

  constructor(private http: HttpClient) {}

  deleteItem() {
    this.http.delete(`http://example.com/api/delete - data/${this.itemId}`).subscribe(response => {
      console.log('Item deleted successfully:', response);
    }, error => {
      console.error('Item deletion failed:', error);
    });
  }
}

在上述代码中,我们通过 http.delete 方法并在 URL 中包含要删除资源的 ID,向服务器发送 DELETE 请求。

5. 处理请求和响应拦截

5.1 请求拦截

请求拦截器允许我们在请求发送之前对请求进行统一的处理,例如添加通用的请求头、处理身份验证等。

首先,创建一个请求拦截器类,实现 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 {
  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const token = localStorage.getItem('token');
    if (token) {
      request = request.clone({
        setHeaders: {
          Authorization: `Bearer ${token}`
        }
      });
    }
    return next.handle(request);
  }
}

在上述代码中,AuthInterceptor 类在请求发送前检查本地存储中是否存在 token,如果存在则通过 request.clone 方法克隆请求并添加 Authorization 头。

然后,在 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({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    HttpClientModule
  ],
  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: AuthInterceptor,
      multi: true
    }
  ],
  bootstrap: [AppComponent]
})
export class AppModule {}

通过上述配置,AuthInterceptor 会对应用中所有的 HTTP 请求进行拦截处理。

5.2 响应拦截

响应拦截器用于在接收到响应后对响应进行统一处理,例如处理错误、解析响应数据等。

创建一个响应拦截器类,同样实现 HttpInterceptor 接口:

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

@Injectable()
export class ResponseInterceptor implements HttpInterceptor {
  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return next.handle(request).pipe(
      tap((event: HttpEvent<any>) => {
        if (event instanceof HttpResponse) {
          console.log('Response received:', event.body);
        }
      }, error => {
        console.error('Response error:', error);
      })
    );
  }
}

在上述代码中,ResponseInterceptor 类通过 tap 操作符对响应进行处理,在接收到响应时打印响应体(如果是成功响应),在发生错误时打印错误信息。

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 { ResponseInterceptor } from './response - interceptor';

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

通过上述配置,ResponseInterceptor 会对应用中所有的 HTTP 响应进行拦截处理。

6. 错误处理

在使用 HttpClient 模块发送请求时,错误处理是非常重要的环节。HttpClient 模块通过 Observableerror 回调来处理请求过程中发生的错误。

6.1 网络错误

当网络连接出现问题,例如服务器不可达、网络中断等,会触发网络错误。

import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Component({
  selector: 'app - network - error',
  templateUrl: './network - error.component.html',
  styleUrls: ['./network - error.component.css']
})
export class NetworkErrorComponent {
  constructor(private http: HttpClient) {}

  makeRequest() {
    this.http.get('http://nonexistent - server.com/api/data').subscribe(response => {
      console.log('Response:', response);
    }, error => {
      if (error.error instanceof ErrorEvent) {
        console.error('Client - side error:', error.error.message);
      } else {
        console.error('Server - side error:', error.status, error.error);
      }
    });
  }
}

在上述代码中,当请求一个不存在的服务器时,会触发错误。通过检查 error.error 是否是 ErrorEvent 实例,可以区分是客户端错误(如网络问题)还是服务器端错误。

6.2 服务器端错误

服务器端可能返回各种错误状态码,例如 404(Not Found)、500(Internal Server Error)等。

import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Component({
  selector: 'app - server - error',
  templateUrl: './server - error.component.html',
  styleUrls: ['./server - error.component.css']
})
export class ServerErrorComponent {
  constructor(private http: HttpClient) {}

  makeRequest() {
    this.http.get('http://example.com/api/nonexistent - resource').subscribe(response => {
      console.log('Response:', response);
    }, error => {
      if (error.status === 404) {
        console.error('Resource not found');
      } else if (error.status === 500) {
        console.error('Internal server error');
      } else {
        console.error('Other server - side error:', error.status, error.error);
      }
    });
  }
}

在上述代码中,根据服务器返回的不同状态码进行针对性的错误处理。

7. 高级技巧:合并请求与缓存

7.1 合并请求

在某些情况下,我们可能需要同时发送多个请求,并在所有请求都完成后进行统一处理。可以使用 forkJoin 操作符来合并多个 Observable,这些 Observable 通常来自多个 HTTP 请求。

假设我们有两个接口,一个获取用户信息,另一个获取用户订单列表,我们需要在两个请求都完成后展示相关信息:

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

@Component({
  selector: 'app - combined - requests',
  templateUrl: './combined - requests.component.html',
  styleUrls: ['./combined - requests.component.css']
})
export class CombinedRequestsComponent {
  user: any;
  orders: any;

  constructor(private http: HttpClient) {}

  fetchData() {
    const userRequest = this.http.get('http://example.com/api/user');
    const ordersRequest = this.http.get('http://example.com/api/orders');

    forkJoin([userRequest, ordersRequest]).subscribe(([user, orders]) => {
      this.user = user;
      this.orders = orders;
      console.log('User:', this.user);
      console.log('Orders:', this.orders);
    }, error => {
      console.error('Combined requests failed:', error);
    });
  }
}

在上述代码中,forkJoin 接受一个 Observable 数组,当所有 Observable 都发出值时,forkJoin 会发出一个包含所有 Observable 发出值的数组。

7.2 缓存请求结果

为了提高性能,减少不必要的网络请求,我们可以对某些请求的结果进行缓存。可以通过 RxJS 的 shareReplay 操作符来实现简单的缓存功能。

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

@Component({
  selector: 'app - cached - request',
  templateUrl: './cached - request.component.html',
  styleUrls: ['./cached - request.component.css']
})
export class CachedRequestComponent {
  data: any;
  private cachedData$: any;

  constructor(private http: HttpClient) {}

  fetchData() {
    if (!this.cachedData$) {
      this.cachedData$ = this.http.get('http://example.com/api/data').pipe(
        shareReplay(1)
      );
    }

    this.cachedData$.subscribe(response => {
      this.data = response;
      console.log('Data:', this.data);
    });
  }
}

在上述代码中,shareReplay(1) 表示缓存最后一个发出的值,并将其重播给新的订阅者。这样,当多次调用 fetchData 方法时,如果 cachedData$ 已经存在,就不会再次发送 HTTP 请求,而是直接使用缓存的数据。

通过以上内容,我们全面深入地了解了 Angular 中 HttpClient 模块的请求体构建与发送相关知识,包括基础和复杂请求体的构建、不同请求方法的使用、拦截器的应用、错误处理以及一些高级技巧。这些知识将帮助开发者更高效地使用 HttpClient 模块与后端服务器进行通信,构建稳定、高性能的前端应用。