webentwicklung-frage-antwort-db.com.de

Datei-Upload mit Angular2 nach REST API

Ich arbeite gerade an einer Spring REST - API mit einer in Angular 2 codierten Schnittstelle.

Mein Problem ist, dass ich mit Angular 2 keine Datei hochladen kann.

Meine Webresources in Java sind: 

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

Und es funktioniert perfekt, wenn ich es über die URL-Anfrage mit Auth-Header usw. anrufe. (mit Advanced Rest Client-Erweiterung für Chrome) 

Beweis: (in diesem Fall funktioniert alles gut) 

 enter image description here Ich habe das hinzugefügt 

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

Spring-Konfigurationsdatei und die Pom-Abhängigkeit 

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

ABER wenn ich versuche, dasselbe mit einem Webformular zu tun:

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

Mit der (Änderungs-) Methode: 

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

Mein Webservice gibt einen Fehler 500 zurück, der sich in den Tomcat-Protokollen befindet: http://Pastebin.com/PGdcFUQb

Ich habe auch die 'Content-Type': undefined-Methode ausprobiert, jedoch ohne Erfolg (der Webservice gibt in diesem Fall einen 415-Fehler zurück.).

Kann mir jemand helfen herauszufinden, was das Problem ist? 

Problem gelöst, ich werde diese Frage später mit meinem Code aktualisieren :), aber schauen Sie sich den Plunker an, es funktioniert einwandfrei . Danke.

44
Slater

Dies ist in der endgültigen Version eigentlich ganz einfach. Ich habe eine Weile gebraucht, um meinen Kopf darum zu wickeln, denn die meisten Informationen darüber, die mir begegnet sind, sind veraltet. Meine Lösung hier veröffentlichen, falls jemand anderes damit zu kämpfen hat.

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

Dann benutze es einfach so:

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

Das ist wirklich alles, was dazu gehört.

Alternativ können Sie das Ereignisobjekt erfassen und die Dateien vom srcElement abrufen. Ich bin mir nicht sicher, ob ein Weg besser ist als der andere, um ehrlich zu sein!

Denken Sie daran, dass FormData IE10 + ist. Wenn Sie also IE9 unterstützen müssen, benötigen Sie eine Polyfill.

Update 2017-01-07

Code wurde aktualisiert, damit mehrere Dateien hochgeladen werden können. Auch in meiner ursprünglichen Antwort fehlte ein ziemlich wichtiges Bit in Bezug auf FormData (da ich die eigentliche Upload-Logik in einen eigenen Dienst in meiner eigenen App verschoben habe, habe ich sie dort bearbeitet).

154
Brother Woodrow

Tatsächlich können Sie derzeit nur Zeichenfolgeneingaben für post, put und patch der Angular2 HTTP-Unterstützung bereitstellen.

Um dies zu unterstützen, müssen Sie das XHR-Objekt direkt nutzen, wie unten beschrieben:

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

Weitere Informationen finden Sie in diesem Programm: https://plnkr.co/edit/ozZqbxIorjQW15BrDFrg?p=info .

Im Angular Repo gibt es ein Problem und eine ausstehende PR zu diesem Thema:

26

Das hat für mich funktioniert:

<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

Das hat für mich funktioniert: Angular 2 bietet eine gute Unterstützung für das Hochladen von Dateien:

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

Ich habe eine Fehlermeldung erhalten: Java.io.IOException: RESTEASY007550: Unable to get boundary for multipart

Um dieses Problem zu lösen, entfernen Sie den "Content-Type" "Multipart/Formulardaten". 

5
heman123

Dieser Thread war so hilfreich, dass ich gezwungen war, meine Lösung mitzuteilen. Bruder Woodrow s Antwort war mein Ausgangspunkt. Ich wollte auch auf Rob Gwynn-Jones 'Kommentar "Stellen Sie sicher, dass Sie den Content-Type-Header nicht manuell setzen" Das ist super wichtig und hat mir eine Menge Zeit gespart.


Diese Version ermöglicht mehrere Hinzufügen/Entfernen-Vorgänge (aus verschiedenen Ordnern), bevor alle Dateien gleichzeitig hochgeladen werden.

Mehrere Dateien mit demselben Namen (aus verschiedenen Ordnern) können zusammen hochgeladen werden, aber dieselbe Datei wird nicht zweimal zur Upload-Liste hinzugefügt (dies ist nicht so trivial, wie es scheint!).

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

Der Rückruf in 'addFiles' ermöglicht das Hochladen außerhalb der Komponente. Die Komponente wird folgendermaßen verwendet:

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

'setFiles' ist der Rückruf. 'this' ist in diesem Kontext die übergeordnete Komponente:

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

Alles, was bleibt, ist das Anhängen der mehrteiligen Nutzdaten vor dem Aufrufen der Upload-API (auch in der übergeordneten Komponente):

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

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

Ich hoffe, dies ist hilfreich und kann bei Bedarf gerne korrigiert/aktualisiert werden. Prost!

2
vanwinter

Wenn Sie nach einer einfachen Lösung suchen und die Kodierung nicht selbst vornehmen möchten, würde ich empfehlen, diese Bibliothek zu verwenden:

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

Dann: 

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