hapi_带有节点和Hapi后端的Angular文件上传

hapi

In this article, we will talk about how to handle file uploads with Angular. We will create an images uploader that allow user to upload single or multiple images file by drag and drop or select file dialog.

在本文中,我们将讨论如何使用Angular处理文件上传。 我们将创建一个图像上传器,允许用户通过拖放或选择文件对话框上传单个或多个图像文件。

We will then upload the selected images and display them accordingly. We will also learn to filter the upload file type, for example, we only allow images, do not allow file type like PDF.

然后,我们将上传选定的图像并进行相应显示。 我们还将学习过滤上传文件的类型,例如,我们只允许图片,不允许像PDF这样的文件类型。

Image uploader

文件上传UI和API ( File Upload UI & API )

File upload consists of two parts: the UI (front-end) and the API (back-end). We will be using Angular to handle the UI part. We need a backend application to accept the uploaded files. You may follow the backend tutorials or download and run either one of these server side application to handle file upload for your backend:-

文件上传由两部分组成: UI(前端)API(后端) 。 我们将使用Angular处理UI部分。 我们需要一个后端应用程序来接受上传的文件。 您可以按照后端教程进行操作,也可以下载并运行以下任一服务器端应用程序来处理后端的文件上传:-

We will be using File upload with Hapi.js as our backend throughout this articles. We will also learn the tricks to enable fake upload on the front-end.

在本文中,我们将使用Hapi.js的文件上传作为我们的后端。 我们还将学习在前端启用假冒上传的技巧。

文件上传组件HTML ( File Upload Component HTML )

Alright, let's start creating our Angular file upload component.

好了,让我们开始创建Angular文件上传组件。

<!-- page-file-upload.component.html -->

<div>
  <!--UPLOAD-->
  <form #f="ngForm" enctype="multipart/form-data" novalidate 
    *ngIf="currentStatus === STATUS_INITIAL || currentStatus === STATUS_SAVING">
    <h1>Upload images</h1>
    <div class="dropbox">
      <input type="file" multiple
        [name]="uploadFieldName" (change)="filesChange($event.target.name, $event.target.files)" 
        [disabled]="currentStatus === STATUS_SAVING" accept="image/*" #photos>
      <p *ngIf="currentStatus === STATUS_INITIAL">
        Drag your file(s) here to begin<br>
        or click to browse
      </p>
      <p *ngIf="currentStatus === STATUS_SAVING">
        Uploading {{ photos.files.length }} files... 
      </p>
    </div>
  </form>
</div>

Notes:-

笔记:-

  1. Our upload form will have a few statuses: STATUS_INITIAL, STATUS_SAVING, STATUS_SUCCESS, STATUS_FAILED, the variable name is pretty expressive themselves.

    我们的上传表单将具有以下几种状态:STATUS_INITIAL,STATUS_SAVING,STATUS_SUCCESS,STATUS_FAILED,变量名本身具有很强的表现力。
  2. We will display the upload form when the status is initial or saving.

    状态为初始或保存时,我们将显示上传表单。
  3. The form attribute enctype="multipart/form-data" is important. To enable file upload, this attribute must be set. Learn more about enctype here.

    表单属性enctype="multipart/form-data"很重要。 要启用文件上传,必须设置此属性。 在此处了解有关enctype的更多信息。
  4. We have a file input <input type="file" /> to accept file upload. The property multiple indicate it's allow multiple file upload. Remove it for single file upload.

    我们有一个文件输入<input type="file" />来接受文件上传。 属性multiple表示允许上传多个文件。 将其删除以上传单个文件。
  5. We will handle the file input change event. Whenever the file input change (someone drop or select files), we will trigger the filesChange function and pass in the control name and selected files $event.target.files, and then upload to server.

    我们将处理文件输入change事件。 每当文件输入更改(有人删除或选择文件)时,我们将触发filesChange函数并传入控件名称和所选文件$event.target.files ,然后上载到服务器。
  6. We limit the file input to accept images only with the attribute accept="image/*".

    我们将文件输入限制为仅接受具有属性accept="image/*"
  7. The file input will be disabled during upload, so user can only drop / select files again after upload complete.

    上传过程中将禁用文件输入,因此用户只能在上传完成后再次删除/选择文件。
  8. We set a template variable #photos to the file input. This gives us a reference to the file input control. Later, you can see we use the photos variable in displaying number of files uploading Uploading {{ photos.files.length }} files....

    我们将模板变量#photos设置为文件输入。 这为我们提供了文件输入控件的参考。 稍后,您可以看到我们使用photos变量显示了上传的文件数。 Uploading {{ photos.files.length }} files...

设置文件上传组件的样式 ( Style our File Upload Component )

Now, that's the interesting part. Currently, our component look like this:

现在,这是有趣的部分。 当前,我们的组件如下所示:

File upload component without styling

We need to transform it to look like this:

我们需要将其转换为如下形式:

File upload component with styling

Let's style it!

让我们来造型​​吧!

/* page-file-upload.component.css */

.dropbox {
    outline: 2px dashed grey; /* the dash box */
    outline-offset: -10px;
    background: lightcyan;
    color: dimgray;
    padding: 10px 10px;
    min-height: 200px; /* minimum height */
    position: relative;
    cursor: pointer;
}

.dropbox:hover {
    background: lightblue; /* when mouse over to the drop zone, change color */
}

input[type="file"] {
    opacity: 0; /* invisible but it's there! */
    width: 100%;
    height: 200px;
    position: absolute;
    cursor: pointer;
}

.dropbox p {
    font-size: 1.2em;
    text-align: center;
    padding: 50px 0;
}

With only few lines of css, our component looks prettier now.

只需几行CSS,我们的组件现在看起来更漂亮了。

Notes:-

笔记:-

  1. We make the file input invisible by applying opacity: 0 style. This doesn't hide the file input, it just make it invisible.

    我们通过应用opacity: 0样式使文件输入不可见。 这不会隐藏文件输入,只会使其不可见。
  2. Then, we style the file input parent element, the dropbox css class. We make it look like a drop file zone surround with dash.

    然后,我们设置文件输入父元素dropbox css类的样式。 我们使它看起来像是一个带有短划线的拖放文件区域。
  3. Then, we align the text inside dropbox to center.

    然后,我们将Dropbox中的文本对齐到中心。

文件上传组件代码 ( File Upload Component Code )

Let's proceed to code our component class.

让我们继续编写我们的组件类。

// page-file-upload.component.ts

import { Component } from '@angular/core';
import { FileUploadService } from './file-upload.service'; // we will create this next!

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

  uploadedFiles = [];
  uploadError;
  currentStatus: number;
  uploadFieldName = 'photos';

  readonly STATUS_INITIAL = 0;
  readonly STATUS_SAVING = 1;
  readonly STATUS_SUCCESS = 2;
  readonly STATUS_FAILED = 3;

  constructor(private _svc: FileUploadService) {
    this.reset(); // set initial state
  }

  filesChange(fieldName: string, fileList: FileList) {
    // handle file changes
    const formData = new FormData();

    if (!fileList.length) return;

    // append the files to FormData
    Array
      .from(Array(fileList.length).keys())
      .map(x => {
        formData.append(fieldName, fileList[x], fileList[x].name);
      });

    // save it
    this.save(formData);
  }

  reset() {
    this.currentStatus = this.STATUS_INITIAL;
    this.uploadedFiles = [];
    this.uploadError = null;
  }

  save(formData: FormData) {
    // upload data to the server
    this.currentStatus = this.STATUS_SAVING;
    this._svc.upload(formData)
      .take(1)
      .delay(1500) // DEV ONLY: delay 1.5s to see the changes
      .subscribe(x => {
        this.uploadedFiles = [].concat(x);
        this.currentStatus = this.STATUS_SUCCESS;
      }, err => {
        this.uploadError = err;
        this.currentStatus = this.STATUS_FAILED;
      })
  }
}

Notes:-

笔记:-

  1. Later on, we will call the Hapi.js file upload API to upload images, the API accept a field call photos. That's our file input field name.

    稍后,我们将调用Hapi.js文件上传 API来上传图片,该API接受现场调用photos 。 那就是我们的文件输入字段名称。
  2. We handle the file changes with the filesChange function. FileList is an object returned by the files property of the HTML element. It allow us to access the list of files selected with the element. Learn more [here]((https://developer.mozilla.org/en/docs/Web/API/FileList).

    我们使用filesChange函数处理文件更改。 FileList是HTML元素的files属性返回的对象。 它允许我们访问用元素选择的文件的列表。 在此处[( https://developer.mozilla.org/en/docs/Web/API/FileList )了解更多信息。
  3. We then create a new FormData, and append all our photos files to it. FormData interface provides a way to easily construct a set of key/value pairs representing form fields and their values. Learn more here.

    然后,我们创建一个新的FormData ,并将所有photos文件附加到该文件。 FormData接口提供了一种轻松构造一组代表表单字段及其值的键/值对的方法。 在这里了解更多。
  4. The save function will call our file upload service (hang on, we will create the service next!). We also set the status according to the result.

    save功能将调用我们的文件上传服务(继续,我们接下来将创建该服务!)。 我们还根据结果设置状态。

文件上传服务 ( File Upload Service )

// file-upload.service.ts

import { Injectable } from '@angular/core';
import { Http, RequestOptionsArgs, Headers } from '@angular/http';

@Injectable()
export class FileUploadService {

    baseUrl = 'http://localhost:3001'; // our local Hapi Js API

    constructor(private _http: Http) { }

    upload(formData) {
        const url = `${this.baseUrl}/photos/upload`;
        return this._http.post(url, formData)
            .map(x => x.json())
            .map((x: any[]) => x
          // add a new field url to be used in UI later
                .map(item => Object
                    .assign({}, item, { url: `${this.baseUrl}/images/${item.id}` }))
            );
    }
}

Nothing much, the code is pretty expressive itself. We upload the files, wait for the result, map it accordingly.

没什么,代码本身也很容易表达。 我们上传文件,等待结果,并相应地映射它。

Now wire up your component and service to module, usually app.module.ts, and run it.

现在,将您的组件和服务连接到模块(通常是app.module.ts )并运行它。

显示成功和失败的结果 ( Display Success and Failed Result )

We can upload the files successfully now. However, there's no indication in UI. Let's update our HTML.

我们现在可以成功上传文件。 但是,UI中没有指示。 让我们更新我们HTML。

<!-- page-file-upload.component.html -->
<div>
  <!--UPLOAD-->
  ...

  <!--SUCCESS-->
  <div class="margin-20" *ngIf="currentStatus === STATUS_SUCCESS">
    <h2>Uploaded {{ uploadedFiles.length }} file(s) successfully.</h2>
    <p>
      <a href="javascript:void(0)" (click)="reset()">Upload again</a>
    </p>
    <ul class="list-unstyled">
      <li *ngFor="let item of uploadedFiles">
        <img [src]="item.url" class="img-responsive img-thumbnail" 
          [alt]="item.originalName">
      </li>
    </ul>
  </div>

  <!--FAILED-->
  <div class="margin-20" *ngIf="currentStatus === STATUS_FAILED">
    <h2>Uploaded failed.</h2>
    <p>
      <a href="javascript:void(0)" (click)="reset()">Try again</a>
    </p>
    <pre>{{ uploadError | json }}</pre>
  </div>
</div>

Notes:-

笔记:-

  1. Display the uploaded image when upload successfully.

    成功上传后显示上传的图像。
  2. Display the error message when upload failed.

    上载失败时显示错误消息。

伪造前端上传 ( Fake the Upload in Front-end )

If you are lazy to start the back-end application (Hapi, Express, etc) to handle file upload. Here is a fake service to replace the file upload service.

如果您懒于启动后端应用程序(Hapi,Express等)来处理文件上传。 这是一项伪造的服务,用来代替文件上传服务。

// file-upload.fake.service.ts

import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Rx';

@Injectable()
export class FileUploadFakeService {

    upload(formData: any) {
        const photos: any[] = formData.getAll('photos');
        const promises = photos.map((x: File) => this.getImage(x)
            .then(img => ({
                id: img,
                originalName: x.name,
                fileName: x.name,
                url: img
            })));
        return Observable.fromPromise(Promise.all(promises));
    }

    private getImage(file: File) {
        return new Promise((resolve, reject) => {
            const fReader = new FileReader();
            const img = document.createElement('img');

            fReader.onload = () => {
                img.src = fReader.result;
                resolve(this.getBase64Image(img));
            }

            fReader.readAsDataURL(file);
        })
    }

    private getBase64Image(img) {
        const canvas = document.createElement('canvas');
        canvas.width = img.width;
        canvas.height = img.height;

        const ctx = canvas.getContext('2d');
        ctx.drawImage(img, 0, 0);

        const dataURL = canvas.toDataURL('image/png');

        return dataURL;
    }
}

Came across this solution in this Stackoverflow post. Pretty useful. My online demo is using this service.

在此Stackoverflow帖子中介绍了此解决方案。 很有用。 我的在线演示正在使用此服务。

Basically, what the code do is read the source, draw it in canvas, and save it as data url with the canvas toDataURL function. Learn more about canvas here.

基本上,代码要做的是读取源代码,将其绘制在画布中,然后使用canvas toDataURL函数将其另存为数据url。 在此处了解有关画布的更多信息。

If you realize, our fake service has a same public interface as the real file upload service, both has upload function and return list of files. This is important for the next step, swap the real file upload service with the fake one.

如果您意识到,我们的虚假服务具​​有与真实文件上传服务相同的公共接口,既具有upload功能,又具有文件返回列表。 这对下一步很重要,将真实文件上传服务与伪造文件交换服务交换。

使用Fake Service交换真实文件上传 ( Swap the Real File Upload with the Fake Service )

First you might think that to use the fake service, you need to register the fake service in module, and import it in our file upload component like how we do usually. However, there's a quicker way, with Angular dependency injection (DI). Let's look at our App module.

首先,您可能会认为要使用伪造的服务,您需要在模块中注册伪造的服务,然后像通常一样将其导入到我们的文件上传组件中。 但是,有了Angular依赖注入(DI),有一种更快的方法。 让我们看看我们的App模块。

// app.module.ts

...
import { PageFileUploadComponent, FileUploadFakeService, 
  FileUploadService } from './file-upload';

@NgModule({
  ...
  providers: [
    // FileUploadService, // normally we do this, comment it, we do the below instead
    { provide: FileUploadService, useClass: FileUploadFakeService }, // we can do this instead

  ],
  ...
})
export class AppModule { }

With this, you don't need to change your component code, stop your backend API, refresh the browser, you should see our app is still working, calling fake service instead.

这样,您无需更改组件代码,停止后端API,刷新浏览器,您应该会看到我们的应用程序仍在运行,而改为调用伪造服务。

In short, Providers: [FileUploadService] is the short form of Providers: [{ provide: FileUploadService, useClass: FileUploadService }]. Therefore, as long as we have another class with similar interface, we can swap it easily.

简而言之, Providers: [FileUploadService]Providers: [{ provide: FileUploadService, useClass: FileUploadService }]的简称Providers: [{ provide: FileUploadService, useClass: FileUploadService }] 。 因此,只要我们有另一个具有相似接口的类,我们就可以轻松地对其进行交换。

Angular DI is powerful. We'll leave that for another post.

Angular DI功能强大。 我们将其留在另一篇文章中。

摘要 ( Summary )

That's it. This is how you can handle file upload without using any 3rd party libraries and plugins in Angular. It isn't that hard right?

而已。 这是您无需使用Angular中的任何第三方库和插件即可处理文件上传的方式。 这不是很难吗?

Happy coding!

编码愉快!

The UI (Front-end)

用户界面(前端)

The API (Back-end)

API(后端)

翻译自: https://scotch.io/tutorials/file-uploads-in-angular-with-a-node-and-hapi-backend

hapi

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值