Angular文件上传:完整指南

1,825 阅读6分钟

Angular文件上传:完整指南

这篇文章将涵盖您在实践中需要了解的所有内容,以便在 Angular 应用程序中处理各种文件上传场景。

我们将学习如何构建一个功能齐全的 Angular 文件上传组件,该组件需要上传具有给定扩展名的文件并通过 HTTP POST 调用将文件发送到后端.

这个自定义组件将有一个上传加载指示器,它也将支持上传取消。我们还将给出一个示例(在 Node 中),说明如何在后端处理文件

目录:

  1. 如何在浏览器中实现文件上传
  2. 构建文件上传组件的用户界面
  3. 使用文件对话框从文件系统中选择对应文件
  4. 通过使用angular http client上传文件到后端
  5. 如何去展示一个文件上传进度指示器
  6. 如何去取消一个正在上传的文件
  7. 在node后端中如何处理一个正在上传的文件
  8. 如何上传多个文件
  9. 总结

如何在浏览器中上传文件

为了构建 Angular 文件上传组件,我们需要首先了解如何仅以纯 HTML 和 Javascript 上传文件。在浏览器中上传文件的关键要素是type为file的input输入框。 此输入将允许用户打开浏览器文件选择对话框并选择一个或多个文件(默认情况下,只有一个文件)。这是此输入的样子:

image.png 使用这个文件输入框,您可以从您的文件系统中选择一个文件,然后使用一些 Javascript,您已经可以将它发送到后端。

为什么我们不经常看到文件输入框?

这种纯文件输入的问题是默认情况下很难设置样式。应用于它的某些样式无法更改,我们甚至无法更改按钮上的文本。这是无法更改的默认浏览器行为,这就是为什么我们通常不会在日常使用的所有界面(如 Gmail 等)上看到此纯文件输入的原因。由于此文件输入无法正确设置样式,因此最常见的选择是实际上永远不会将其显示给最终用户,正如我们将看到的那样。

file类型的input输入框是如何工作的

当用户使用文件上传对话框选择一个文件时,一个事件变化将被触发。此事件将包含用户在 target.files 属性上选择的文件列表。 当更改事件被触发时,文件不会被浏览器自动上传到后端。相反,我们需要自己触发一个 HTTP 请求,以响应更改事件。 现在我们知道了所有标准文件上传浏览器功能是如何工作的,让我们看看如何构建一个漂亮的 Angular 组件来封装它。

构建文件上传组件的用户界面

因为类型文件的普通输入不可能正确设置样式,所以我们最终做的是对用户隐藏它,然后构建一个在幕后使用文件输入的替代文件上传 UI。

这是初始文件上传组件的模板:

<input type="file" class="file-input"
   (change)="onFileSelected($event)" #fileload>
 
<div class="file-upload">
   {{fileName || "No file uploaded yet."}}
    <button mat-mini-fab color="primary" class="upload-btn"
      (click)="fileUpload.click()">
        <mat-icon>attach_file</mat-icon>
    </button>
</div>

该用户界面分为两个独立的部分。在顶部,我们有一个纯文件输入,用于打开文件上传对话框和处理更改事件 这个纯输入文本对用户是隐藏的,正如我们在组件 CSS 中看到的那样:

.file-input {
   display: none;
}

在隐藏文件输入下方,我们有文件上传容器 div,其中包含用户将在屏幕上看到的实际 UI。例如,我们使用 Angular Material 组件构建了这个 UI,当然,替代文件上传 UI 可以采用您喜欢的任何形式. 这个 UI 可以是一个对话框,一个拖放区,或者就像我们的组件一样,只是一个样式化的按钮:

image.png 请注意组件模板中的上传蓝色按钮和不可见文件输入是如何链接的。当用户单击蓝色按钮时,单击处理程序会通过 fileUpload.click() 触发文件输入。然后用户将从文件上传对话框中选择一个文件,更改事件将由onFileSelected() 触发和处理

通过使用angular http client上传文件到后端

现在让我们看看我们的组件类和实现 onFileSelected():

@Component({
  selector: 'file-upload',
  templateUrl: "file-upload.component.html",
  styleUrls: ["file-upload.component.scss"]
})
export class FileUploadComponent {

    fileName = '';

    constructor(private http: HttpClient) {}

    onFileSelected(event) {

        const file:File = event.target.files[0];

        if (file) {

            this.fileName = file.name;

            const formData = new FormData();

            formData.append("thumbnail", file);

            const upload$ = this.http.post("/api/thumbnail-upload", formData);

            upload$.subscribe();
        }
    }
}

组件中发生的事:

  1. 我们通过event.target.files属性获取用户选择的文件
  2. 通过FormData api建立表单数据
  3. 然后我们使用angular HTTP Client去创建http请求并发送文件给后端

此时,我们已经有了一个可以工作的文件上传组件。但我们希望将此组件更进一步。我们希望能够向用户显示进度指示器,并且还支持上传取消

如何去展示一个文件上传进度指示器

我们将向文件上传组件的 UI 添加更多元素。这是文件上传组件模板的最终版本:

<input type="file" class="file-input"
       [accept]="requiredFileType"
       (change)="onFileSelected($event)" #fileUpload>

<div class="file-upload">

   {{fileName || "No file uploaded yet."}}

    <button mat-mini-fab color="primary" class="upload-btn"
      (click)="fileUpload.click()">
        <mat-icon>attach_file</mat-icon>

    </button>

</div>

<div class="progress">

  <mat-progress-bar class="progress-bar" mode="determinate"
                    [value]="uploadProgress" *ngIf="uploadProgress">

  </mat-progress-bar>

  <mat-icon class="cancel-upload" (click)="cancelUpload()" 
            *ngIf="uploadProgress">delete_forever</mat-icon>

</div>

我们添加到 UI 的两个主要元素是:

  1. Angular Material 进度条,仅在文件上传仍在进行时可见。
  2. 取消上传按钮,也仅在上传仍在进行时可见

如何知道上传了多少文件?

我们实现进度指示器的方式是使用 Angular HTTP 客户端的 reportProgress 特性。 使用此功能,我们可以通过 HTTP Observable 发出的多个事件通知文件上传的进度。 要实际查看它,让我们看一下文件上传组件类的最终版本,它实现了所有功能:

@Component({
  selector: 'file-upload',
  templateUrl: "file-upload.component.html",
  styleUrls: ["file-upload.component.scss"]
})
export class FileUploadComponent {

    @Input()
    requiredFileType:string;

    fileName = '';
    uploadProgress:number;
    uploadSub: Subscription;

    constructor(private http: HttpClient) {}

    onFileSelected(event) {
        const file:File = event.target.files[0];
      
        if (file) {
            this.fileName = file.name;
            const formData = new FormData();
            formData.append("thumbnail", file);

            const upload$ = this.http.post("/api/thumbnail-upload", formData, {
                reportProgress: true,
                observe: 'events'
            })
            .pipe(
                finalize(() => this.reset())
            );
          
            this.uploadSub = upload$.subscribe(event => {
              if (event.type == HttpEventType.UploadProgress) {
                this.uploadProgress = Math.round(100 * (event.loaded / event.total));
              }
            })
        }
    }

  cancelUpload() {
    this.uploadSub.unsubscribe();
    this.reset();
  }

  reset() {
    this.uploadProgress = null;
    this.uploadSub = null;
  }
}

如我们所见,我们在 HTTP 调用中将 reportProgress 属性设置为 true,并且还将 observe 属性设置为值事件。这意味着,随着 POST 调用的继续,我们将收到报告 HTTP 请求进度的事件对象。

这些事件将作为 http$ Observable 的值发出,并以不同的类型出现:

  1. UploadProgress 类型的事件报告已上传文件的百分比
  2. Response 类型的事件表明上传已经完成

使用 UploadProgress 类型的事件,我们将正在进行的上传百分比保存在成员变量 uploadProgress 中,然后我们使用它来更新进度指示条的值。

当上传完成或失败时,我们需要向用户隐藏进度条。我们可以通过使用 RxJs finalize 运算符来确保我们这样做,它将在两种情况下调用 reset() 方法:上传成功或失败。

如何去取消一个正在上传的文件

为了支持文件上传取消,我们所要做的就是保留对订阅 http$ Observable 时获得的 RxJs 订阅对象的引用。在我们的组件中,我们将这个订阅对象存储在 uploadSub 成员变量中。

当上传仍在进行时,用户可能会通过单击取消按钮来决定取消上传。然后cancelUpload()上传方法将被触发,并且可以通过取消uploadSub订阅来取消HTTP请求。

此取消订阅将立即触发,取消正在进行的文件上传。

如何只接受某种类型的文件

在文件上传组件的最终版本中,我们可以通过使用 requiredFileType 属性要求用户上传某种类型的文件:

<file-upload requiredFileType="image/png"></file-upload>

然后,此属性直接传递给文件上传模板中文件输入的接受属性,强制用户从文件上传对话框中选择一个 png 文件。

如何上传多个文件

默认情况下,浏览器文件选择对话框将允许用户仅选择一个文件进行上传。 但是使用 multiple 属性,我们可以允许用户选择多个文件:

<input type="file" class="file-upload" multiple>

请注意,这需要一个与我们构建的完全不同的 UI。带进度指示器的样式化上传按钮仅适用于上传单个文件。

在node后端中如何处理一个正在上传的文件

我们需要先安装 express-fileupload 包。然后我们可以将这个包作为中间件添加到我们的 Express 应用程序中:

const fileUpload = require('express-fileupload');

const app: Application = express();

app.use(fileUpload());

从这里开始,我们所要做的就是定义一个 Express 路由来处理文件上传请求:

app.route('/api/thumbnail-upload').post(onFileupload);

export function onFileupload(req:Request, res: Response) {

  let file = req['files'].thumbnail;

  console.log("File uploaded: ", file.name);
  ...
}

总结

在 Angular 中处理文件上传的最佳方式是构建一个或多个自定义组件,具体取决于支持的上传场景。

文件上传组件需要在内部包含文件类型的 HTML 输入,允许用户从文件系统中选择一个或多个文件。

这个文件输入应该对用户隐藏,因为它不可设置样式,并被更友好的 UI 取代。

在背景中使用input输入框,我们可以通过更改事件获取对文件的引用,然后我们可以使用它来构建 HTTP 请求并将文件发送到后端。