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 jav
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).
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.
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!
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,
});
this.uploader.onBeforeUploadItem = function(item) {
item.url = URL.replace('?', "?param1=value1");
}
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:
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