web-dev-qa-db-fra.com

Téléchargement de fichier avec Angular2 vers REST API

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) 

 enter image description here J'ai ajouté le 

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

44
Slater

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

154
Brother Woodrow

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:

26
Thierry Templier

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)
        )
}}
11
Anil Samal

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" 

5
heman123

Si vous cherchez une solution simple et que vous ne voulez pas coder vous-même, je vous recommanderais d'utiliser cette bibliothèque:

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

1
gnast
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>
0
Tejal Fegade