在Angular中如何上传文件?

207

我知道这是一个非常一般性的问题,但我在Angular 2中无法上传文件。

我尝试过:

1)http://valor-software.com/ng2-file-upload/

2)http://ng2-uploader.com/home

...但都失败了。有没有人能在Angular中上传文件?你使用了什么方法?如何实现?如果提供任何样例代码或演示链接,将不胜感激。

14个回答

423
Angular 2提供了很好的上传文件支持,无需第三方库。
<input type="file" (change)="fileChange($event)" placeholder="Upload file" accept=".pdf,.doc,.docx">

fileChange(event) {
    let fileList: FileList = event.target.files;

    if (fileList.length < 1) {
      return;
    }
    
    let file: File = fileList[0];
    let formData:FormData = new FormData();
    formData.append('uploadFile', file, file.name)
    
    let headers = new Headers();
    /** In Angular 5, including the header Content-Type can invalidate your request */
    headers.append('Content-Type', 'multipart/form-data');
    headers.append('Accept', 'application/json');

    let options = new RequestOptions({ headers: headers });

    this.http.post(`${this.apiEndPoint}`, formData, options)
        .map(res => res.json())
        .catch(error => Observable.throw(error))
        .subscribe(
            data => console.log('success'),
            error => console.log(error)
        );
}

使用"@angular/core": "~2.0.0"和"@angular/http": "~2.0.0"


7
无效,至少在我的情况下是这样。sailsJs服务器接收到了一个空的文件数组/对象。 - Kaleem Ullah
22
除了这一行代码:headers.append('enctype', 'multipart/form-data');,其他的都对我起作用。在这一行代码里,我需要做出修改,用'enctype'代替'Content-Type'。也许这要取决于服务器端的代码(例如API)。 - Ariful Islam
42
如果Angular团队能够就这个问题撰写一些文档,那将是非常好的。我在他们的文档中找不到任何相关内容。这份代码示例已经过时,无法与v4+版本兼容。 - Rob B
16
对于某些应用服务器,设置内容类型会被拒绝。你需要让它保持空白:let headers = new Headers(); 浏览器会自动处理好一切。 - PeterS
8
LMFAO花费了20分钟才意识到根本不需要设置标题,这真是一坨垃圾。提醒其他人在使用具有.Net webapi的angular4.x.x时,不要尝试设置标题!感谢@PeterS指出这一点。 - Jota.Toledo
显示剩余15条评论

88

根据上面的答案,我用Angular 5.x构建了这个项目

只需调用 uploadFile(url, file).subscribe() 就可以触发上传

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

@Injectable()
export class UploadService {

  constructor(private http: HttpClient) { }

  // file from event.target.files[0]
  uploadFile(url: string, file: File): Observable<HttpEvent<any>> {

    let formData = new FormData();
    formData.append('upload', file);

    let params = new HttpParams();

    const options = {
      params: params,
      reportProgress: true,
    };

    const req = new HttpRequest('POST', url, formData, options);
    return this.http.request(req);
  }
}

在您的组件中像这样使用它

  // At the drag drop area
  // (drop)="onDropFile($event)"
  onDropFile(event: DragEvent) {
    event.preventDefault();
    this.uploadFile(event.dataTransfer.files);
  }

  // At the drag drop area
  // (dragover)="onDragOverFile($event)"
  onDragOverFile(event) {
    event.stopPropagation();
    event.preventDefault();
  }

  // At the file input element
  // (change)="selectFile($event)"
  selectFile(event) {
    this.uploadFile(event.target.files);
  }

  uploadFile(files: FileList) {
    if (files.length == 0) {
      console.log("No file selected!");
      return

    }
    let file: File = files[0];

    this.upload.uploadFile(this.appCfg.baseUrl + "/api/flash/upload", file)
      .subscribe(
        event => {
          if (event.type == HttpEventType.UploadProgress) {
            const percentDone = Math.round(100 * event.loaded / event.total);
            console.log(`File is ${percentDone}% loaded.`);
          } else if (event instanceof HttpResponse) {
            console.log('File is completely loaded!');
          }
        },
        (err) => {
          console.log("Upload Error:", err);
        }, () => {
          console.log("Upload done");
        }
      )
  }

6
可以与Angular6很好地配合使用。谢谢。您需要导入这些库:import {HttpClient,HttpParams,HttpRequest,HttpEvent,HttpEventType,HttpResponse} from'@angular/common/http'; - Bharathiraja
1
在我的情况下,我正在使用授权令牌,并添加了这个额外的代码 let params = new HttpParams(); let headers = new HttpHeaders({ 'Authorization': 'Bearer ' + localStorage.getItem('accessToken'), }); const options = { headers: headers, params: params, reportProgress: true, }; - Ciprian Dragoe
值得注意的是,如果您愿意使用类型推断来为uploadFile()函数提供返回类型,则可以完全省略ObservableHttpEvent的导入! this.http.request()已经返回了Observable<HttpEvent<{}>>类型,因此如果您给请求调用一个泛型类型(即this.http.request<any>()),那么整个函数就会正确地工作出正确的类型。 - wosevision
2
HTML部分如下:input type="file" (change)="addFiles($event)" style="display: none" #file multiple> <button mat-raised-button color="primary" (click)="selectFile($event)">上传文件</button> - Shantam Mittal
在UploadService中,formData.append('upload', file);中的'upload'是什么意思? - ShaileshDev

26

感谢 @Eswar 提供的帮助。这段代码对我非常有效。我想在解决方案中添加一些内容:

我遇到了一个错误:java.io.IOException: RESTEASY007550: 无法获取多部分的边界

为了解决这个错误,你应该删除 "Content-Type" "multipart/form-data",这样就可以解决问题了。


8
如果您删除 Content-Type,它将正确生成。例如:multipart/form-data; boundary=---------------------------186035562730765173675680113。另请参阅 https://dev59.com/I14c5IYBdhLWcg3wssFs#29697774 和 https://github.com/angular/angular/issues/11819。 - turdus-merula
1
我遇到了这个错误 java.io.IOException: org.apache.tomcat.util.http.fileupload.FileUploadException: the request was rejected because no multipart boundary was found",和你的情况类似。但是当我删除 Content-Type 头时,后端返回 404 错误。我们正在使用 Spring 和 Angular 2。任何帮助都将不胜感激。 - Helen
这应该只是对他回答的评论,不是吗? - MMalke
谢谢,但为什么使用“Content-type”头部不起作用? - Aboubacar Ouattara

20

由于代码示例有点过时,我想分享一种更近期的方法,使用Angular 4.3和新的HttpClient API,即@angular/common/http。

export class FileUpload {

@ViewChild('selectedFile') selectedFileEl;

uploadFile() {
let params = new HttpParams();

let formData = new FormData();
formData.append('upload', this.selectedFileEl.nativeElement.files[0])

const options = {
    headers: new HttpHeaders().set('Authorization', this.loopBackAuth.accessTokenId),
    params: params,
    reportProgress: true,
    withCredentials: true,
}

this.http.post('http://localhost:3000/api/FileUploads/fileupload', formData, options)
.subscribe(
    data => {
        console.log("Subscribe data", data);
    },
    (err: HttpErrorResponse) => {
        console.log(err.message, JSON.parse(err.error).error.message);
    }
)
.add(() => this.uploadBtn.nativeElement.disabled = false);//teardown
}

1
你有这个的HTML代码吗?我喜欢它使用HttpParams。不知道你是否有一个完整的工作示例。谢谢。 - Maddy
如何将多个文件作为数组一起上传?它应该如何附加到表单数据对象中? - SSR
请查看多部分表单数据 https://www.webdavsystem.com/javaserver/doc/resumable_upload/multipart_post/ - jsaddwater

19
在Angular 2+中,非常重要的是让`Content-Type`保持为空。如果将'Content-Type'设置为'multipart/form-data',则上传功能将无法正常工作! upload.component.html
<input type="file" (change)="fileChange($event)" name="file" />

upload.component.ts

export class UploadComponent implements OnInit {
    constructor(public http: Http) {}

    fileChange(event): void {
        const fileList: FileList = event.target.files;
        if (fileList.length > 0) {
            const file = fileList[0];

            const formData = new FormData();
            formData.append('file', file, file.name);

            const headers = new Headers();
            // It is very important to leave the Content-Type empty
            // do not use headers.append('Content-Type', 'multipart/form-data');
            headers.append('Authorization', 'Bearer ' + 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9....');
            const options = new RequestOptions({headers: headers});

            this.http.post('https://api.mysite.com/uploadfile', formData, options)
                 .map(res => res.json())
                 .catch(error => Observable.throw(error))
                 .subscribe(
                     data => console.log('success'),
                     error => console.log(error)
                 );
        }
    }
}

8

请问这个是否兼容NG2? - G1P
@G1P 它肯定是兼容Angular 4的。https://www.primefaces.org/primeng/#/setup - makkasi

8
这个简单的解决方案对我有用:file-upload.component.html
<div>
  <input type="file" #fileInput placeholder="Upload file..." />
  <button type="button" (click)="upload()">Upload</button>
</div>

然后直接使用XMLHttpRequest在组件中进行上传。

import { Component, OnInit, ViewChild } from '@angular/core';

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

  @ViewChild('fileInput') fileInput;

  constructor() { }

  ngOnInit() {
  }

  private upload() {
    const fileBrowser = this.fileInput.nativeElement;
    if (fileBrowser.files && fileBrowser.files[0]) {
      const formData = new FormData();
      formData.append('files', fileBrowser.files[0]);
      const xhr = new XMLHttpRequest();
      xhr.open('POST', '/api/Data/UploadFiles', true);
      xhr.onload = function () {
        if (this['status'] === 200) {
            const responseText = this['responseText'];
            const files = JSON.parse(responseText);
            //todo: emit event
        } else {
          //todo: error handling
        }
      };
      xhr.send(formData);
    }
  }

}

如果您正在使用dotnet core,则参数名称必须与from字段名称匹配。在这种情况下,文件为:

[HttpPost("[action]")]
public async Task<IList<FileDto>> UploadFiles(List<IFormFile> files)
{
  return await _binaryService.UploadFilesAsync(files);
}

这篇答案是http://blog.teamtreehouse.com/uploading-files-ajax的抄袭。

编辑: 上传后,您需要清除文件上传,以便用户可以选择新文件。而且,与其使用XMLHttpRequest,也许使用fetch更好:

private addFileInput() {
    const fileInputParentNative = this.fileInputParent.nativeElement;
    const oldFileInput = fileInputParentNative.querySelector('input');
    const newFileInput = document.createElement('input');
    newFileInput.type = 'file';
    newFileInput.multiple = true;
    newFileInput.name = 'fileInput';
    const uploadfiles = this.uploadFiles.bind(this);
    newFileInput.onchange = uploadfiles;
    oldFileInput.parentNode.replaceChild(newFileInput, oldFileInput);
  }

  private uploadFiles() {
    this.onUploadStarted.emit();
    const fileInputParentNative = this.fileInputParent.nativeElement;
    const fileInput = fileInputParentNative.querySelector('input');
    if (fileInput.files && fileInput.files.length > 0) {
      const formData = new FormData();
      for (let i = 0; i < fileInput.files.length; i++) {
        formData.append('files', fileInput.files[i]);
      }

      const onUploaded = this.onUploaded;
      const onError = this.onError;
      const addFileInput = this.addFileInput.bind(this);
      fetch('/api/Data/UploadFiles', {
        credentials: 'include',
        method: 'POST',
        body: formData,
      }).then((response: any) => {
        if (response.status !== 200) {
          const error = `An error occured. Status: ${response.status}`;
          throw new Error(error);
        }
        return response.json();
      }).then(files => {
        onUploaded.emit(files);
        addFileInput();
      }).catch((error) => {
        onError.emit(error);
      });
    }

https://github.com/yonexbat/cran/blob/master/cranangularclient/src/app/file-upload/file-upload.component.ts


3

上传带有表单字段的图像

SaveFileWithData(article: ArticleModel,picture:File): Observable<ArticleModel> 
{

    let headers = new Headers();
    // headers.append('Content-Type', 'multipart/form-data');
    // headers.append('Accept', 'application/json');

let requestoptions = new RequestOptions({
  method: RequestMethod.Post,
  headers:headers
    });



let formData: FormData = new FormData();
if (picture != null || picture != undefined) {
  formData.append('files', picture, picture.name);
}
 formData.append("article",JSON.stringify(article));

return this.http.post("url",formData,requestoptions)
  .map((response: Response) => response.json() as ArticleModel);
} 

在我的情况下,我需要使用C#编写的.NET Web Api

// POST: api/Articles
[ResponseType(typeof(Article))]
public async Task<IHttpActionResult> PostArticle()
{
    Article article = null;
    try
    {

        HttpPostedFile postedFile = null;
        var httpRequest = HttpContext.Current.Request;

        if (httpRequest.Files.Count == 1)
        {
            postedFile = httpRequest.Files[0];
            var filePath = HttpContext.Current.Server.MapPath("~/" + postedFile.FileName);
            postedFile.SaveAs(filePath);
        }
        var json = httpRequest.Form["article"];
         article = JsonConvert.DeserializeObject <Article>(json);

        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }

        article.CreatedDate = DateTime.Now;
        article.CreatedBy = "Abbas";

        db.articles.Add(article);
        await db.SaveChangesAsync();
    }
    catch (Exception ex)
    {
        int a = 0;
    }
    return CreatedAtRoute("DefaultApi", new { id = article.Id }, article);
}

3
今天我将ng2-file-upload包集成到我的Angular 6应用程序中,这非常简单,请查看下面的高级代码。
导入ng2-file-upload模块。 app.module.ts
    import { FileUploadModule } from 'ng2-file-upload';

    ------
    ------
    imports:      [ FileUploadModule ],
    ------
    ------

请在 Component 的 ts 文件中导入 FileUploader。

app.component.ts

    import { FileUploader, FileLikeObject } from 'ng2-file-upload';
    ------
    ------
    const URL = 'http://localhost:3000/fileupload/';
    ------
    ------

     public uploader: FileUploader = new FileUploader({
        url: URL,
        disableMultipart : false,
        autoUpload: true,
        method: 'post',
        itemAlias: 'attachment'

        });

      public onFileSelected(event: EventEmitter<File[]>) {
        const file: File = event[0];
        console.log(file);

      }
    ------
    ------

组件HTML添加文件标签

app.component.html

 <input type="file" #fileInput ng2FileSelect [uploader]="uploader" (onFileSelected)="onFileSelected($event)" />

在线工作的 stackblitz 链接:https://ng2-file-upload-example.stackblitz.io

Stackblitz 代码示例:https://stackblitz.com/edit/ng2-file-upload-example

官方文档链接:https://valor-software.com/ng2-file-upload/


3

这是一个有用的教程,讲述了如何使用ng2-file-upload和WITHOUT ng2-file-upload上传文件。

对我来说,它非常有帮助。

目前,该教程包含一些错误:

1- 客户端上传url应与服务器相同,在app.component.ts 中更改以下行:

const URL = 'http://localhost:8000/api/upload';

更改为

const URL = 'http://localhost:3000';

2- 服务器将响应作为“text/html”发送,因此在 app.component.ts 中更改

.post(URL, formData).map((res:Response) => res.json()).subscribe(
  //map the success function and alert the response
  (success) => {
    alert(success._body);
  },
  (error) => alert(error))

to

.post(URL, formData)  
.subscribe((success) => alert('success'), (error) => alert(error));

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接