En fait, je travaille sur une API Spring REST avec une interface codée en Angular 2.
Mon problème est que je ne peux pas télécharger un fichier avec Angular 2.
Mes ressources Web en Java sont les suivantes:
@RequestMapping(method = RequestMethod.POST, value = "/upload")
public String handleFileUpload(@RequestParam MultipartFile file) {
//Dosomething
}
Et cela fonctionne parfaitement lorsque je l’appelle via une requête d’URL avec en-tête Auth, etc. (avec l'extension Advanced Rest Client pour Chrome)
Preuve: (tout fonctionne bien dans ce cas)
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver" />
Fichier de configuration Spring et dépendance Pom
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.2</version>
</dependency>
MAIS lorsque j'essaie de faire la même chose avec un formulaire Web:
<input type="file" #files (change)="change(files)"/>
<pre>{{fileContents$|async}}</pre>
Avec la méthode (change):
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'));
*/
}
Mon service Web me renvoie une erreur 500, qui figure dans les journaux Tomcat: http://Pastebin.com/PGdcFUQb
J'ai aussi essayé la méthode 'Content-Type': undefined
mais sans succès (le service Web m'a renvoyé une erreur 415 dans ce cas.
Quelqu'un peut-il m'aider à comprendre quel est le problème?
Problème résolu, je mettrai à jour cette question plus tard avec mon code :) mais regardez sur le plunker, il fonctionne parfaitement bien… .. Merci.
C'est vraiment très facile à faire dans la version finale. Il m'a fallu un peu de temps pour comprendre, car la plupart des informations que j'ai trouvées à ce sujet sont périmées. Afficher ma solution ici au cas où quelqu'un d'autre aurait des problèmes avec cela.
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
}
}
}
Ensuite, utilisez-le comme ceci:
<file-upload #fu (change)="fu.upload()" [multiple]="true"></file-upload>
C'est vraiment tout ce qu'il y a à faire.
Vous pouvez également capturer l'objet événement et récupérer les fichiers à partir de srcElement. Je ne sais pas si un moyen est meilleur qu'un autre, pour être honnête!
N'oubliez pas que FormData est IE10 +. Si vous devez prendre en charge IE9, vous aurez besoin d'un polyfill.
Mise à jour 2017-01-07
Code mis à jour pour pouvoir gérer le téléchargement de plusieurs fichiers. De plus, ma réponse initiale manquait d'un élément crucial concernant FormData (puisque j'ai déplacé la logique de téléchargement réelle vers un service distinct dans ma propre application, je la traitais là-bas).
En fait, pour le moment, vous pouvez uniquement saisir des chaînes pour les méthodes post
, put
et patch
du support HTTP Angular2.
Pour cela, vous devez exploiter directement l'objet XHR, comme décrit ci-dessous:
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);
});
}
}
Voir ce plunkr pour plus de détails: https://plnkr.co/edit/ozZqbxIorjQW15BrDFrg?p=info .
Il y a un problème et un PR en attente à ce sujet dans le rapport angulaire:
Cela a fonctionné pour moi:
<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)
)
}}
Cela a fonctionné pour moi: Angular 2 fournit un bon support pour télécharger un fichier:
<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)
)
}
}
Je recevais une erreur: Java.io.IOException: RESTEASY007550: Unable to get boundary for multipart
Afin de résoudre ce problème, vous devez supprimer le "Content-Type" "multipart/form-data"
Si vous cherchez une solution simple et que vous ne voulez pas coder vous-même, je vous recommanderais d'utiliser cette bibliothèque:
this.uploader.onBeforeUploadItem = function(item) {
item.url = URL.replace('?', "?param1=value1");
}
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));
}
Ensuite:
<form (ngSubmit)="upload()">
<input type="file" id="file" multiple (change)="fileUpload($event.target.files)">
<button type="submit">Upload</button>
</form>