Angular - 响应



HTTP 响应

在 HTTP 协议中,响应是服务器为客户端请求的特定资源返回的数据。响应将遵循与请求相同的模式。它有一个头部和一个主体。头部包含有关响应的元信息,主体包含实际的响应。

HttpEvent 类

HttpClient 将请求发送到服务器并捕获来自服务器的响应。然后,它将响应包含在一个对象中,该对象根据请求配置具有以下可能的类型。

  • HttpEvent
  • HttpSentEvent
  • HttpUserEvent
  • HttpProgressEvent
  • HttpResponseBase
  • HttpHeaderResponse
  • HttpResponse

实际上,HttpEvent 是所有其他类的并集,如下所示:

type HttpEvent<T> = HttpSentEvent | HttpHeaderResponse | HttpResponse<T> | 
   HttpProgressEvent | HttpUserEvent<T>;

让我们在本节中逐一了解 Angular 提供的响应类型。

HttpSentEvent

HttpSentEvent 用于指定请求已发送到服务器。当请求多次重试时,它将非常有用。

interface HttpSentEvent {
   type: HttpEventType.Sent
}

HttpUserEvent

HttpUserEvent 用于标识响应的事件是用户定义的。它将有助于将所有自定义事件分组到一个类别中。它将确保所有拦截器都能正确处理和转发事件。

interface HttpUserEvent<T> {
   type: HttpEventType.User
}

HttpProgressEvent

HttpProgressEvent 用于标识请求是基于下载还是基于上传。此外,它还将在下载/上传功能期间包含当前已加载的字节。

interface HttpProgressEvent {
   type: HttpEventType.DownloadProgress | HttpEventType.UploadProgress
   loaded: number
   total?: number
}

这里:

  • loaded 用于引用已上传/下载的字节数

  • total 用于引用要下载/上传的总数据量

HttpResponseBase

HttpResponseBaseHttpHeaderResponseHttpResponse 的基类。它包含有关响应的基本信息。

abstract class HttpResponseBase {  
   constructor() // have not shown full details for understanding purpose   
   headers: HttpHeaders
   status: number
   statusText: string
   url: string | null
   ok: boolean
   type: HttpEventType.Response | HttpEventType.ResponseHeader
}

这里:

  • headers - 作为 HttpHeaders 对象的响应头信息

  • status - 用于引用请求的不同状态的数字

  • statusText - 用于引用请求的不同状态的文本

  • url - 请求的 URL

  • ok - 请求的成功/失败

  • type - 事件的类型(Response 或 ResponseHeader)

HttpHeaderResponse

HttpHeaderResponse 继承自 HttpResponseBase,并包含一个克隆响应的选项。该类的目的是包含带有头部和状态信息的响应,而跳过响应的实际主体。它只有一个 clone() 方法,用于获取头部信息的副本,以便更新头部信息。

let response = res.clone(update: { })

这里:

  • res 是从服务器返回的响应对象。

  • update 是一个对象,其中包含要在响应的头部中更新的数据。

HttpResponse

HttpResponse 继承自 HttpResponseBase,并包含响应的主体和克隆响应的选项。该类的目的是包含带有主体、头部和状态信息的响应。它只有一个 clone() 方法,用于获取整个响应的副本,以便更新响应信息。

可以使用 body 属性获取响应的主体,如下所示:

let body = res.body;

这里:

  • res 是从服务器返回的响应对象。

  • body 是保存来自服务器的实际响应的属性。

克隆响应的方法与 HttpHeaderResponse 类似,如下所示:

let response = res.clone(update: { })

这里:

  • res 是从服务器返回的响应对象。

  • update 是一个对象,其中包含要在响应的头部中更新的数据。

工作示例

让我们创建一个示例 Web 应用程序,将文件上传到服务器。我们将创建一个用于上传文件的 api,然后从 Angular 前端应用程序调用它。在应用程序开发过程中,我们将学习不同类型的响应。

首先,让我们创建一个新的 express 应用程序来将文件上传到服务器。

步骤 1:转到您喜欢的 workspace,如下所示:

cd /go/to/your/favorite/workspace

步骤 2:创建一个新文件夹 expense-rest-api 并进入该文件夹

mkdir upload-rest-api && cd upload-rest-api

步骤 3:使用 npm 命令提供的 init 子命令创建一个新应用程序,如下所示:

npm init

上述命令会提出一些问题,请使用默认答案回答所有问题。

步骤 4:安装 express 和 cors 包以创建基于节点的 Web 应用程序。

npm install express cors multer --save

这里:

  • express 是一个用于创建 Web 应用程序的 Web 框架

  • cors 是一个用于处理 HTTP 应用程序中 CORS 概念的中间件

  • multer 是另一个用于处理文件上传的中间件

步骤 5:打开 index.js 并更新以下代码:

var express = require("express")
var cors = require('cors')
const multer = require('multer');

var app = express()
app.use(cors());

var bodyParser = require("body-parser");
app.use(express.urlencoded({ extended: true }));
app.use(express.json());

var HTTP_PORT = 8000
app.listen(HTTP_PORT, () => {
   console.log("Server running on port %PORT%".replace("%PORT%", HTTP_PORT))
});

const storage = multer.diskStorage({
   destination: (req, file, cb) => {
      cb(null, "uploads/")
   },
   filename: (req, file, cb) => {
      cb(null, Date.now() + "-" + file.originalname)
   },
})

const upload = multer({ storage: storage });
app.post('/api/upload', upload.single('photo'), (req, res) => {
   console.log(req.file)
   res.json({ message: 'File uploaded successfully!' });
});

这里:

  • 通过启用 cors、multer 和 body 解析器中间件来配置一个简单的 express 应用程序。

  • 创建了一个新的 api /api/upload 来接受文件并将其存储在服务器上的 uploads 文件夹中。

  • 将上传文件夹配置为 uploads

  • 该 api 将接受名为 photo 的文件输入

步骤 6:创建一个用于存储上传文件的目录。

mkdir uploads

步骤 7:运行应用程序,如下所示:

node index.js

步骤 8:要测试应用程序并确保其正常工作,请使用 postman、curl 或任何其他 HTTP 客户端工具包。我们可以向 api 端点 https://127.0.0.1:8000/api/upload 创建一个新请求,其中 photo 为文件类型的表单输入,并将文件附加到表单输入。

让我们创建一个可工作的 Angular 示例,使用 HttpClient 服务类和 HttpRequest 选项从服务器获取所有费用项目。

步骤 1:通过运行 ng new 命令创建一个新的 Angular 应用程序,如下所示:

ng new my-upload-app

启用 Angular 路由和 CSS,如下所示:

? Would you like to add Angular routing? Yes
? Which stylesheet format would you like to use? CSS

步骤 2:通过在模块配置文件 (app.module.ts) 中导入 HttpClientModule 来启用应用程序中的 HTTP 通信。

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';

import { HttpClientModule } from '@angular/common/http';

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

这里:

  • @angular/common/http 模块导入 HttpClientModule。

  • 将 HttpClientModule 添加到 @NgModule 配置的 imports 部分。

步骤 3:创建一个新组件 Upload 来显示来自服务器的费用项目。

ng generate component upload

它将创建如下所示的组件:

CREATE src/app/upload/upload.component.css (0 bytes)
CREATE src/app/upload/upload.component.html (21 bytes)
CREATE src/app/upload/upload.component.spec.ts (559 bytes)
CREATE src/app/upload/upload.component.ts (202 bytes)
UPDATE src/app/app.module.ts (554 bytes)

步骤 4:将我们的新组件包含到 App 根组件的视图 app.component.html 中,如下所示:

<app-upload></app-upload>

<router-outlet></router-outlet>

步骤 5:通过构造函数将 HttpClient 注入到 Upload 组件中,并从 rxjs 和 Angular 模块导入必要的类,如下所示:

import { Component } from '@angular/core';
import { HttpClient, HttpEvent, HttpEventType } from '@angular/common/http';
import { Observable, map } from 'rxjs';

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

步骤 6:创建一个变量来保存要上传的文件,以及另一个变量来保存上传消息。

file?: File | null = null;
message : String | null = null;

步骤 7:创建一个函数来获取用户从表单(待创建)上传的文件,并将其存储在 file 变量中。

onFilechange(event: any) {
   let files = event.target.files
   this.file = files.item(0)
   console.log(this.file)
}

这里:

  • event 是保存上传事件信息的事件对象。event.target.files 保存上传的文档。

步骤 8:创建一个函数 getEventMessage() 来打印上传事件信息。

private getEventMessage(event: HttpEvent, file?: File) {
   let message : String | null = null;
   switch (event.type) {
      case HttpEventType.Sent:
         message = `Uploading file "${file?.name}" of size ${file?.size}.`;
         console.log(message);
         return message;
   
      case HttpEventType.UploadProgress:
         // Compute and show the % done:
         const percentDone = event.total ? Math.round(100 * event.loaded / event.total) : 0;
         message = `File "${file?.name}" is ${percentDone}% uploaded.`;
         console.log(message);
         return message;
   
      case HttpEventType.Response:
         message = `File "${file?.name}" was completely uploaded!`;
         console.log(message);
         return message;
   
      default:
         message = `File "${file?.name}" surprising upload event: ${event.type}.`;
         console.log(message);
         return message;
   }
}

这里:

  • switch 语句用于捕获不同的事件并相应地打印它们

  • HttpEventType 保存信息类型

步骤 9:创建一个函数 upload() 将用户选择的文件上传到服务器。

upload() {
   const formData: FormData = new FormData();
   formData.append('photo', this.file as Blob, this.file?.name);
   const myObservable: Observable<HttpEvent<any>> = 
      this.http.post<any>('https://127.0.0.1:8000/upload', formData, { observe: 'events',
         reportProgress: true });
   
   myObservable.pipe(
      map(data => { console.log(data); return data; }),).subscribe(
         evt => { 
            this.message = this.getEventMessage(evt, this.file as File)
      });
}

这里:

  • formData 保存用户上传的文件。

  • post() 方法将 formData 中的数据发送到服务器。

  • myObservable 将使用 map 函数打印服务器返回的数据,并使用 getEventMessage() 函数打印事件信息

步骤 10:upload 组件 (upload.component.ts) 的完整源代码如下:

import { Component } from '@angular/core';
import { HttpClient, HttpEvent, HttpEventType } from '@angular/common/http';
import { Observable, map } from 'rxjs';

@Component({
   selector: 'app-upload',
   templateUrl: './upload.component.html',
   styleUrls: ['./upload.component.css']
})
export class UploadComponent {

   file?: File | null = null;
   message : String | null = null; 
   
   constructor(private http: HttpClient) { }
   
   onFilechange(event: any) {
      let files = event.target.files
      this.file = files.item(0)
      console.log(this.file)
   }
   
   upload() {
   const formData: FormData = new FormData();
   formData.append('photo', this.file as Blob, this.file?.name);
   const myObservable: Observable<HttpEvent<any>> = 
      this.http.post<any>('https://127.0.0.1:8000/api/upload',
      formData, { observe: 'events', reportProgress: true });
   
   console.log('Hi');
   
   myObservable.pipe(
      map(data => { console.log(data); return data; }),
      ).subscribe(
         evt => { 
            this.message = this.getEventMessage(evt, this.file as File)
         });
   }
   
   private getEventMessage(event: HttpEvent<any>, file?: File) {
      let message : String | null = null;
      switch (event.type) {
         case HttpEventType.Sent:
            message = `Uploading file "${file?.name}" of size ${file?.size}.`;
            console.log(message);
            return message;
      
         case HttpEventType.UploadProgress:
            // Compute and show the % done:
            const percentDone = event.total ? Math.round(100 * event.loaded / event.total) : 0;
            message = `File "${file?.name}" is ${percentDone}% uploaded.`;
            console.log(message);
            return message;
      
         case HttpEventType.Response:
            message = `File "${file?.name}" was completely uploaded!`;
            console.log(message);
            return message;
      
         default:
            message = `File "${file?.name}" surprising upload event: ${event.type}.`;
            console.log(message);
            return message;
      }
   }
}

步骤 11:在组件的模板('upload.component.html')中创建一个上传表单,并为上传按钮的 click 事件设置 upload() 方法。

<div><h3>Uploads</h3></div>
<form enctype="multipart/form-data">
   <label for="formFile" class="form-label">Upload file example</label>
   <input type="file" name="photo" id="file" (change)="this.onFilechange($event)" />
   
   <div *ngIf="file">
      <section class="file-info">
         File details:
         <ul>
            <li>Name: {{file.name}}</li>
            <li>Type: {{file.type}}</li>
            <li>Size: {{file.size}} bytes</li>
         </ul>
      </section>
      <p *ngIf="message">{{message}}</p>
   
      <button (click)="this.upload()" type="button">Upload</button>
   </div>
</form>

步骤 12:最后,使用以下命令运行应用程序:

ng serve

步骤 13:打开浏览器并导航到 https://127.0.0.1:4200/ URL 并检查输出

uploads

这里,输出显示表单。选择一个大约 30 MB 的大文件并尝试上传它,如下所示:

file upload upload file example picture can't displayed
  • 输出显示浏览器及其控制台中服务器返回的所有事件。

结论

Angular 提供了不同的类和类型来正确包含来自服务器的响应数据。所有类都易于学习、理解和操作响应,然后将其显示在网站/应用程序中。

广告