File Upload with Angular2 to REST API

SpringAngularSpring MvcAngular2 FormsAngular2 Http

Spring Problem Overview


Actually, I'm working on a Spring REST API with an interface coded in Angular 2.

My problem is I can't upload a file with Angular 2.

My Webresources in java is that :

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

And it is perfectly working when I call it through URL request with Auth header etc ... ( with Advanced Rest Client extension for Chrome )

Proof: (everything works fine in that case )

enter image description here I added the

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

Spring config file and the Pom dependency

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

BUT when I try to do the same thing with a webform :

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

With the (change) method :

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'));
    */
}

My web service returns me an error 500, with that in tomcat logs: http://pastebin.com/PGdcFUQb

I tried the 'Content-Type': undefined method too but without success ( the web service return me a 415 error in that case.

Can someone help me to figure out what's the problem is?

Problem solved, I'll update that question later with my code :) but, have a look on the plunker it's working perfectly well. Thanks.

Spring Solutions


Solution 1 - Spring

This is actually really easy to do in the final release. Took me a while to wrap my head around it because most information about it that I've come across is outdated. Posting my solution here in case anyone else is struggling with this.

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

Then just use it like so:

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

That is really all there is to it.

Alternatively, capture the event object and get the files from the srcElement. Not sure if any way is better than the other, to be honest!

Keep in mind FormData is IE10+, so if you have to support IE9 you'll need a polyfill.

Update 2017-01-07

Updated code to be able to handle uploading of multiple files. Also my original answer was missing a rather crucial bit concerning FormData (since I moved the actual upload logic to a separate service in my own app I was handling it there).

Solution 2 - Spring

In fact, at the moment, you can only provide string input for post, put and patch methods of the Angular2 HTTP support.

To support that, you need to leverage the XHR object directly, as described below:

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

See this plunkr for more details: https://plnkr.co/edit/ozZqbxIorjQW15BrDFrg?p=info.

There is a an issue and a pending PR regarding this in the Angular repo:

Solution 3 - Spring

This has worked for me:

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

Solution 4 - Spring

This has worked for me: Angular 2 provides good support to upload file:

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

I was getting error : java.io.IOException: RESTEASY007550: Unable to get boundary for multipart

In order to solve this you should remove the "Content-Type" "multipart/form-data"

Solution 5 - Spring

This thread has been so helpful that I felt compelled to share my solution. Brother Woodrow's answer was my starting point. I also wanted to draw attention to Rob Gwynn-Jones' comment "make sure not to manually set the Content-Type header" which is super-important and saved me a ton of time.


This version allows multiple add/remove operations (from different folders), before uploading all files at once.

Multiple files with the same name (from different folders) can be uploaded together, but the same file won't be added to the upload list twice (this is not as trivial as it seems!).

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

The callback in 'addFiles' allows the upload to happen outside the component. Component is used like this:

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

'setFiles' is the callback. 'this' in this context is the parent component:

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

All that remains is to attach the multipart payload before calling the upload API (also in the parent component):

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

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

Hope this is helpful, and happy to fix/update if necessary. Cheers!

Solution 6 - Spring

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

Solution 7 - Spring

If your looking for a simple solution and don't want to do the coding yourself, I would recommend using this library:

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

Solution 8 - Spring

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

Then:

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

Solution 9 - Spring

I was just removed content-type from the header. for example this is our header:

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

What you have to do is to just remove Content-Type from this. Like:

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionSlaterView Question on Stackoverflow
Solution 1 - SpringBrother WoodrowView Answer on Stackoverflow
Solution 2 - SpringThierry TemplierView Answer on Stackoverflow
Solution 3 - SpringAnil SamalView Answer on Stackoverflow
Solution 4 - Springheman123View Answer on Stackoverflow
Solution 5 - SpringvanwinterView Answer on Stackoverflow
Solution 6 - SpringAkbar jan- JEE developerView Answer on Stackoverflow
Solution 7 - SpringgnastView Answer on Stackoverflow
Solution 8 - SpringTejal FegadeView Answer on Stackoverflow
Solution 9 - SpringHammad AhmadView Answer on Stackoverflow