使用 Angular2 将文件上传到 REST API

Posted

技术标签:

【中文标题】使用 Angular2 将文件上传到 REST API【英文标题】:File Upload with Angular2 to REST API 【发布时间】:2016-07-21 00:41:13 【问题描述】:

实际上,我正在开发一个带有 Angular 2 编码接口的 Spring REST API。

我的问题是我无法使用 Angular 2 上传文件。

我在java中的网络资源是:

@RequestMapping(method = RequestMethod.POST, value = "/upload")
public String handleFileUpload(@RequestParam MultipartFile file) 
    //Dosomething 

当我通过带有 Auth 标头等的 URL 请求调用它时,它可以正常工作... (带有适用于 Chrome 的 Advanced Rest Client 扩展)

证明:(在这种情况下一切正常)

我添加了

<bean id="multipartResolver"
      class="org.springframework.web.multipart.commons.CommonsMultipartResolver" />

Spring 配置文件和 Pom 依赖

<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.2</version>
</dependency>

但是当我尝试使用网络表单做同样的事情时:

<input type="file" #files (change)="change(files)"/>
<pre>fileContents$|async</pre>

用(更改)方法:

change(file) 
    let formData = new FormData();
    formData.append("file", file);
    console.log(formData);
    let headers = new Headers(
        'Authorization': 'Bearer ' + this.token,
        'Content-Type': 'multipart/form-data'
    );
    this.http.post(this.url, formData, headers).map(res => res.json()).subscribe((data) => console.log(data));
    /*
    Observable.fromPromise(fetch(this.url,
        method: 'post', body: formData,
        headers: this.headers
    )).subscribe(()=>console.log('done'));
    */

我的网络服务返回一个错误 500,并在 tomcat 日志中显示:http://pastebin.com/PGdcFUQb

我也尝试了'Content-Type': undefined 方法,但没有成功(在这种情况下,Web 服务返回 415 错误。

有人可以帮我找出问题所在吗?

问题解决了,我稍后会用我的代码更新这个问题 :) 但是,看看 plunker,它工作得很好。 谢谢。

【问题讨论】:

***.com/q/35985347/5043867 的可能副本 【参考方案1】:

实际上,目前只能为Angular2 HTTP支持的postputpatch方法提供字符串输入。

为了支持这一点,您需要直接利用 XHR 对象,如下所述:

import Injectable from 'angular2/core';
import Observable from 'rxjs/Rx';

@Injectable()
export class UploadService 
  constructor () 
    this.progress$ = Observable.create(observer => 
      this.progressObserver = observer
    ).share();
  

  private makeFileRequest (url: string, params: string[], files: File[]): Observable 
    return Observable.create(observer => 
      let formData: FormData = new FormData(),
        xhr: XMLHttpRequest = new XMLHttpRequest();

      for (let i = 0; i < files.length; i++) 
        formData.append("uploads[]", files[i], files[i].name);
      

      xhr.onreadystatechange = () => 
        if (xhr.readyState === 4) 
          if (xhr.status === 200) 
            observer.next(JSON.parse(xhr.response));
            observer.complete();
           else 
            observer.error(xhr.response);
          
        
      ;

      xhr.upload.onprogress = (event) => 
        this.progress = Math.round(event.loaded / event.total * 100);

        this.progressObserver.next(this.progress);
      ;

      xhr.open('POST', url, true);
      xhr.send(formData);
    );
  

查看此 plunkr 了解更多详情:https://plnkr.co/edit/ozZqbxIorjQW15BrDFrg?p=info。

在 Angular 存储库中有一个问题和待处理的 PR:

https://github.com/angular/angular/issues/10424 https://github.com/angular/angular/pull/7310/files

【讨论】:

Huuum 什么是 xhr 对象? (我正在寻找解决方案)而且我的 webstrom 不承认“progressObeserver”是一个特殊的类还是什么? XMLHttpRequest 对象。浏览器提供的执行 AJAX 请求的对象。 Angular2 HTTP 模块依赖于它。见github.com/angular/angular/blob/master/modules/angular2/src/… 和github.com/angular/angular/blob/master/modules/angular2/src/…。 xhr.open('POST', url, true); 您可以在调用open方法后使用setRequestHeader_xhr.setRequestHeader(name, value); OMFG!它工作得很好!我需要重构和清理那些乱七八糟的东西,但它正在工作!谢谢 !真的 !我以后能问你一些关于那种东西的问题吗?喜欢:“我如何处理 xhr.send 的响应?【参考方案2】:

这在最终版本中实际上很容易做到。我花了一些时间来理解它,因为我遇到的大多数关于它的信息都已经过时了。在这里发布我的解决方案,以防其他人为此苦苦挣扎。

import  Component, ElementRef, Input, ViewChild  from '@angular/core';
import  Http  from '@angular/http';

@Component(
    selector: 'file-upload',
    template: '<input type="file" [multiple]="multiple" #fileInput>'
)
export class FileUploadComponent 
    @Input() multiple: boolean = false;
    @ViewChild('fileInput') inputEl: ElementRef;

    constructor(private http: Http) 

    upload() 
        let inputEl: htmlInputElement = this.inputEl.nativeElement;
        let fileCount: number = inputEl.files.length;
        let formData = new FormData();
        if (fileCount > 0)  // a file was selected
            for (let i = 0; i < fileCount; i++) 
                formData.append('file[]', inputEl.files.item(i));
            
            this.http
                .post('http://your.upload.url', formData)
                // do whatever you do...
                // subscribe to observable to listen for response
        
    

然后像这样使用它:

<file-upload #fu (change)="fu.upload()" [multiple]="true"></file-upload>

这就是它的全部内容。

或者,捕获事件对象并从 srcElement 获取文件。老实说,不确定是否有任何方式比其他方式更好!

记住 FormData 是 IE10+,所以如果你必须支持 IE9,你需要一个 polyfill。

2017-01-07 更新

更新的代码能够处理多个文件的上传。此外,我最初的答案缺少关于 FormData 的一个相当关键的部分(因为我将实际的上传逻辑移到了我自己的应用程序中的一个单独的服务中,所以我在那里处理它)。

【讨论】:

当然!但是,当您搜索“angular 2 file upload”时,这个问题是在 Google 上出现的第一个问题。我想我会添加一些最新信息。 您使用的是哪个版本的 RC?有没有其他人成功使用这个?似乎不适合我。 好吧,我有点困惑,文件上传似乎正在工作,但奇怪的是,由于我使用 localhost:3000 运行,上传无法找到上传文件夹(可能是因为路线?)当我只是链接本地主机/文件夹时,我收到一个 cors 错误,对此有什么想法吗? 如果其他人想知道 FormData 来自哪里,它是本机 FormData 对象:developer.mozilla.org/en-US/docs/Web/API/FormData/FormData 你不需要导入任何东西。 这很好用!一个警告 - 确保不要手动设置 Content-Type 标头。不指定它,Angular 2 似乎选择了正确的值,至少在 application/json 和 multipart/form-data 之间【参考方案3】:

这对我有用: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 > 0) 
        let file: File = fileList[0];
        let formData:FormData = new FormData();
        formData.append('uploadFile', file, file.name);
        let headers = new Headers();
        headers.append('Content-Type', 'multipart/form-data');
        headers.append('Accept', 'application/json');
        let options = new RequestOptions( headers: headers );
        this.http.post(URL, formData, options)
            .map(res => res.json())
            .catch(error => Observable.throw(error))
            .subscribe(
                data => console.log('success'),
                error => console.log(error)
            )
    

我遇到了错误:java.io.IOException: RESTEASY007550: Unable to get boundary for multipart

为了解决这个问题,您应该删除“Content-Type”“multipart/form-data”

【讨论】:

解决方案是旧的,答案来自 angular 2 Beta 15 ^^ 哦。我用过它,它对我有用!您能否提供更新文件上传的链接或代码。我应该在哪里寻找更新的代码! @斯莱特 Huuum idk 那是我使用的解决方案,从那以后我没有研究过它...... srry :/ 尝试查看这个问题中的 cmets 和代码片段 ;) 只需评论 headers.append('Content-Type', 'multipart/form-data');在代码中【参考方案4】:

这对我有用:

<input type="file" (change)="onChange($event)" required class="form-control " name="attach_file" id="attach_file">
onChange(event: any) 
    let fileList: FileList = event.target.files;
if(fileList.length > 0) 
    let file: File = fileList[0];
    let formData:FormData = new FormData();
    formData.append('degree_attachment', file, file.name);
    let headers = new Headers();
    headers.append('Accept', 'application/json');
    let options = new RequestOptions( headers: headers );
    this.http.post('http://url', formData,options)
        .map(res => res.json())
        .catch(error => Observable.throw(error))
        .subscribe(
            data => console.log('success'),
            error => console.log(error)
        )

【讨论】:

【参考方案5】:

如果您正在寻找一个简单的解决方案并且不想自己编写代码,我建议您使用这个库:

https://www.npmjs.com/package/angular2-http-file-upload

【讨论】:

【参考方案6】:
this.uploader.onBeforeUploadItem = function(item) 
  item.url = URL.replace('?', "?param1=value1");

【讨论】:

【参考方案7】:

这个帖子非常有帮助,我觉得有必要分享我的解决方案。 Brother Woodrow 的回答是我的出发点。我还想提请注意Rob Gwynn-Jones' 评论 “确保不要手动设置 Content-Type 标头”,这非常重要,为我节省了大量时间。


此版本允许在一次上传所有文件之前进行多次添加/删除操作(从不同文件夹)。

多个同名文件(来自不同文件夹)可以一起上传,但同一个文件不会被添加到上传列表两次(这并不像看起来那么简单!)。

import  Component, ElementRef, Input, ViewChild  from '@angular/core';
import  Http  from '@angular/http';

@Component(
    selector: 'file-upload',
    template: '<input type="file" [multiple]="multiple" #fileInput>'
)
export class FileUploadComponent 
    @Input() multiple: boolean = false;
    @ViewChild('fileInput') inputEl: ElementRef;

    files: Array<any> = [];
    fileObjects: Array<any> = [];
    fileKeys: Array<string> = [];
    fileCount: number = 0;

    constructor(private http: Http) 

    addFiles(callback: any) 

        const inputEl: HTMLInputElement = this.inputEl.nativeElement;
        const newCount: number = inputEl.files.length;

        for (let i = 0; i < newCount; i ++) 

            const obj = 
                name: inputEl.files[ i ].name,
                type: inputEl.files[ i ].type,
                size: inputEl.files[ i ].size,
                ts: inputEl.files[ i ].lastModifiedDate
            ;

            const key = JSON.stringify(obj);

            if ( ! this.fileKeys.includes(key)) 

                this.files.push(inputEl.files.item(i));
                this.fileObjects.push(obj);
                this.fileKeys.push(key);
                this.fileCount ++;
            
        

        callback(this.files);
    

    removeFile(obj: any) 

        const key: string = JSON.stringify(obj);

        for (let i = 0; i < this.fileCount; i ++) 

            if (this.fileKeys[ i ] === key) 

                this.files.splice(i, 1);
                this.fileObjects.splice(i, 1);
                this.fileKeys.splice(i, 1);
                this.fileCount --;

                return;
            
        
    

“addFiles”中的回调允许在组件外部进行上传。组件是这样使用的:

&lt;file-upload #fu (change)="fu.addFiles(setFiles.bind(this))" [multiple]="true"&gt;&lt;/file-upload&gt;

'setFiles' 是回调。 'this' 在这种情况下是父组件:

    setFiles(files: Array&lt;any&gt;)  this.files = files; 

剩下的就是在调用上传 API 之前附加多部分有效负载(也在父组件中):

const formData = new FormData();
            
for (let i = 0; i < this.files.length; i ++) 

    formData.append('file[]', this.files[ i ]);

希望这对您有所帮助,并乐意在必要时进行修复/更新。干杯!

【讨论】:

【参考方案8】:
fileUpload() 
  const formData = new FormData();

  const files = this.filesToUpload;
  for (let i = 0; i < files.length; i++) 
    formData.append('file', files.item(i));
    formData.append('Content-Type', 'application/json');
    formData.append('Accept', `application/json`);
  


  this.http.post('http://localhost:8080/UploadFile', formData).subscribe(response => console.log(response));

然后:

<form (ngSubmit)="upload()">
    <input type="file" id="file" multiple (change)="fileUpload($event.target.files)">
    <button type="submit">Upload</button>
</form>

【讨论】:

上传
考虑在此答案中添加一些上下文,说明您的解决方案为何解决了 OP 问题。【参考方案9】:

我刚刚从标题中删除了内容类型。例如这是我们的标题:

 let headers = new Headers(
        'Authorization': 'Bearer ' + this.token,
        'Content-Type': 'multipart/form-data'
);

你要做的就是从这里删除Content-Type。喜欢:

 let headers = new Headers(
        'Authorization': 'Bearer ' + this.token,
    );

【讨论】:

以上是关于使用 Angular2 将文件上传到 REST API的主要内容,如果未能解决你的问题,请参考以下文章

使用 Angular2 将 MultipartFile 作为请求参数发送到 REST 服务器

使用 AFNetworking 将 .zip 文件上传到 REST 服务器失败

如何使用 REST API 将文件和附件上传到 sobject 记录?

使用 rest api 从 Web 应用程序将文件上传到 Azure 文件存储

Android将文件作为ByteArray上传到WCF REST服务

如何在 C# 中正确使用 WCF REST API 上的 Stream 将文件(图像/视频/等)上传到服务器?