50

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.

1

9 Answers 9

157

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

14
  • 36
    Sure! But this question is the first one that comes up on Google when you search for "angular 2 file upload". Figured I'd add some up to date information.CommentedOct 15, 2016 at 22:03
  • 2
    What version of RC are you on? Has anyone else had success using this? Doesn't seem to be working for me.CommentedDec 20, 2016 at 21:19
  • 1
    Okay I am a little bit confused, the fileupload seems to be working but the weird thing is, that since im running with localhost:3000 the upload cant find the uploadfolder (maybe because of the routes?) and when I just link localhost/folder I get a cors error any ideas on this?
    – stackg91
    CommentedFeb 4, 2017 at 6:09
  • 4
    If anyone else was wondering where FormData came from, its the native FormData object: developer.mozilla.org/en-US/docs/Web/API/FormData/FormData You dont need to import anything.
    – Brad
    CommentedMar 16, 2017 at 21:26
  • 2
    This works great! One caveat - make sure not to manually set the Content-Type header. Leaving it unspecified, Angular 2 seems to choose the right value, at least between application/json and multipart/form-dataCommentedApr 5, 2017 at 5:13
27

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:

8
  • Huuum what is a xhr object ? ( i'm looking on the solution ) And my webstrom don't recognize the "progressObeserver" is it a special class or something ?
    – Slater
    CommentedApr 1, 2016 at 9:24
  • The XMLHttpRequest object. The object provided by the browser to execute AJAX request. The Angular2 HTTP module relies on it. See github.com/angular/angular/blob/master/modules/angular2/src/… and github.com/angular/angular/blob/master/modules/angular2/src/….CommentedApr 1, 2016 at 9:26
  • xhr.open('POST', url, true); <= is it a way to had my headers ? for Authorization token ?
    – Slater
    CommentedApr 1, 2016 at 9:42
  • You can use the setRequestHeader after having called the open method: _xhr.setRequestHeader(name, value);CommentedApr 1, 2016 at 9:45
  • OMFG ! It's working perfectly ! I need to refactor and clean up that mess but it's working ! Thanks ! really ! Can I ask you later some questions on that kind of stuff ? Like : " how can i Handle the response of the xhr.send ?
    – Slater
    CommentedApr 1, 2016 at 9:54
12

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

    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"

    7
    • the solution is old and the answer is from angular 2 Beta 15 ^^
      – Slater
      CommentedOct 25, 2016 at 12:02
    • Oh. I used it and it worked for me ! can you please provide with a link or code for updated file upload. And where should i look for the updated code! @Slater
      – heman123
      CommentedOct 25, 2016 at 14:16
    • Huuum idk that was the solution i used and i did not worked on it since that ... srry :/
      – Slater
      CommentedOct 26, 2016 at 7:04
    • Try to look on the comments and pieces of code in this question ;)
      – Slater
      CommentedOct 26, 2016 at 7:05
    • 1
      Just comment headers.append('Content-Type', 'multipart/form-data'); in the codeCommentedDec 7, 2016 at 7:27
    3

    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!

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

        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

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

          Then:

          <form (ngSubmit)="upload()"> <input type="file" id="file" multiple (change)="fileUpload($event.target.files)"> <button type="submit">Upload</button> </form> 
          1
          • 1
            Consider adding some context to this answer on why your solution fixes OPs question.
            – Trent
            CommentedSep 30, 2018 at 19:00
          0

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

            Start asking to get answers

            Find the answer to your question by asking.

            Ask question

            Explore related questions

            See similar questions with these tags.