使用Angular2将文件上传到REST API

时间:2016-04-01 09:15:14

标签: spring angular spring-mvc angular2-forms angular2-http

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

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

我的java中的Webresources是:

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

当我通过带有Auth标头等的URL请求调用它时,它完全正常工作...  (使用Chrome的Advanced Rest Client扩展程序)

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

enter image description here 我添加了

<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>

但是当我尝试用webform做同样的事情时:

<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方法但没有成功(在这种情况下,网络服务会给我一个415错误。

有人可以帮我弄清问题是什么?

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

9 个答案:

答案 0 :(得分:156)

这在最终版本中实际上很容易做到。我花了一段时间来绕过它,因为我遇到的关于它的大部分信息已经过时了。在这里发布我的解决方案,以防其他人在努力解决这个问题。

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的一个相当重要的一点(因为我将实际的上传逻辑移动到我自己的应用程序中的一个单独的服务,我在那里处理它。)

答案 1 :(得分:26)

事实上,目前,您只能为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:

答案 2 :(得分:12)

这对我有用:

<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)
        )
}}

答案 3 :(得分:5)

这对我有用:Angular 2为上传文件提供了很好的支持:

 dd <- data.table(date = c("2015-07-01 00:00:12", "2015-07-01 00:00:13","2015-07-01 00:00:14","2015-07-01 00:00:15", "2015-07-01 00:00:16", "2015-07-01 00:00:17","2015-07-01 00:00:18", "2015-07-01 00:00:19", "2015-07-01 00:00:20","2015-07-01 00:00:21", "2015-07-01 00:00:22", "2015-07-01 00:00:23","2015-07-01 00:00:24", "2015-07-01 00:00:25"), status = c(0,0,0,0,1,1,1,0,0,1,1,1,1,0))

我收到了错误:<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) ) } }

为了解决这个问题,你应该删除&#34; Content-Type&#34; &#34;多部分/格式数据&#34;

答案 4 :(得分:2)

这个帖子非常有用,我觉得有必要分享我的解决方案。 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'中的回调允许上传发生在组件外部。组件使用如下:

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

'setFiles'是回调。在此上下文中,“this”是父组件:

    setFiles(files: Array<any>) { this.files = files; }

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

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

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

希望这很有帮助,并且很乐意在必要时修复/更新。干杯!

答案 5 :(得分:1)

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

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

答案 6 :(得分:1)

this.uploader.onBeforeUploadItem = function(item) {
  item.url = URL.replace('?', "?param1=value1");
}

答案 7 :(得分:0)

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>

答案 8 :(得分:0)

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

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

您要做的就是从其中删除Content-Type。喜欢:

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