2015-09-06 8 views
49

Ich verwende Angular, TypeScript, um eine Datei zusammen mit JSON-Daten an einen Server zu senden.Angular - POST hochgeladene Datei

Unten ist mein Code:

import {Component, View, NgFor, FORM_DIRECTIVES, FormBuilder, ControlGroup} from 'angular2/angular2'; 
import {Http, Response, Headers} from 'http/http'; 


@Component({ selector: 'file-upload' }) 
@View({ 
directives: [FORM_DIRECTIVES], 
template: ` 
<h3>File Upload</h3> 

<div> 
    Select file: 
    <input type="file" (change)="changeListener($event)"> 
</div> 

` 
}) 
export class FileUploadCmp { 

public file: File; 
public url: string; 
headers: Headers; 


constructor(public http: Http) { 
    console.log('file upload Initialized'); 
    //set the header as multipart   
    this.headers = new Headers(); 
    this.headers.set('Content-Type', 'multipart/form-data'); 
    this.url = 'http://localhost:8080/test'; 
} 

//onChange file listener 
changeListener($event): void { 
    this.postFile($event.target); 
} 

//send post file to server 
postFile(inputValue: any): void { 

    var formData = new FormData(); 
    formData.append("name", "Name"); 
    formData.append("file", inputValue.files[0]); 

    this.http.post(this.url +, 
     formData , 
     { 
      headers: this.headers 

     }); 
} 

} 

Wie kann ich die formData in String umwandeln und sie an den Server schicken? Ich erinnere mich an AngularJS (v1) Sie würden transformRequest verwenden.

+0

kann nicht die Daten, bevor this.http.post Linie transformiert werden? http ist eine beobachtbare und nimmt Optionen, versuchen Sie, die Quelle suchen, finden Sie ein Beispiel, https: // github.com/angular/eckig/blob/8ed22ce6e7ce0e00c12b036d02627424c6b4ff35/modules/angular2/src/http/http.ts –

+0

Hattest du Fortschritte dabei? – Canastro

+0

Es gibt eine ausstehende Feature-Anfrage auf GitHub https://github.com/angular/angular/issues/2803 – martin

Antwort

40

Blick auf meinem Code, aber bewusst sein. Ich benutze async/await, da die neueste Chrome-Beta überhaupt keinen es6-Code lesen kann, der nach TypScript mit Kompilierung kommt. Sie müssen also asyns/await durch ersetzen.

Eingangswechsel-Handler:

/** 
* @param fileInput 
*/ 
public psdTemplateSelectionHandler (fileInput: any){ 
    let FileList: FileList = fileInput.target.files; 

    for (let i = 0, length = FileList.length; i < length; i++) { 
     this.psdTemplates.push(FileList.item(i)); 
    } 

    this.progressBarVisibility = true; 
} 

Senden Handler:

public async psdTemplateUploadHandler(): Promise<any> { 
    let result: any; 

    if (!this.psdTemplates.length) { 
     return; 
    } 

    this.isSubmitted = true; 

    this.fileUploadService.getObserver() 
     .subscribe(progress => { 
      this.uploadProgress = progress; 
     }); 

    try { 
     result = await this.fileUploadService.upload(this.uploadRoute, this.psdTemplates); 
    } catch (error) { 
     document.write(error) 
    } 

    if (!result['images']) { 
     return; 
    } 

    this.saveUploadedTemplatesData(result['images']); 
    this.redirectService.redirect(this.redirectRoute); 
} 

FileUploadService. Dieser Dienst speichert auch den laufenden Upload-Vorgang $ property, und an anderen Stellen können Sie ihn abonnieren und alle 500ms einen neuen Wert erhalten.

import { Component } from 'angular2/core'; 
import { Injectable } from 'angular2/core'; 
import { Observable } from 'rxjs/Observable'; 
import 'rxjs/add/operator/share'; 

@Injectable() 
export class FileUploadService { 
/** 
* @param Observable<number> 
*/ 
private progress$: Observable<number>; 

/** 
* @type {number} 
*/ 
private progress: number = 0; 

private progressObserver: any; 

constructor() { 
    this.progress$ = new Observable(observer => { 
     this.progressObserver = observer 
    }); 
} 

/** 
* @returns {Observable<number>} 
*/ 
public getObserver(): Observable<number> { 
    return this.progress$; 
} 

/** 
* Upload files through XMLHttpRequest 
* 
* @param url 
* @param files 
* @returns {Promise<T>} 
*/ 
public upload (url: string, files: File[]): Promise<any> { 
    return new Promise((resolve, reject) => { 
     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) { 
        resolve(JSON.parse(xhr.response)); 
       } else { 
        reject(xhr.response); 
       } 
      } 
     }; 

     FileUploadService.setUploadUpdateInterval(500); 

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

/** 
* Set interval for frequency with which Observable inside Promise will share data with subscribers. 
* 
* @param interval 
*/ 
private static setUploadUpdateInterval (interval: number): void { 
    setInterval(() => {}, interval); 
} 
} 
+0

Ich musste auch hinzufügen: 'xhr.setRequestHeader ('X-Requested- Mit ',' XMLHttpRequest '); 'damit es funktioniert – patad

+0

@patad: hast du ein Problem damit? Wenn ich eine XML-Datei hochlade, wird automatisch '------ WebKitFormBoundaryklb8qUzyCQJSI440 Content-Disposition: Formulardaten hinzugefügt; Name = "Datei" 'in meiner Datei. Ich möchte während der Übertragung keine zusätzlichen Daten zur Datei hinzufügen. Wissen Sie, wie Sie damit umgehen sollen? Und um ein Bild wie jpg hochzuladen, war es nach dem Hochladen keine jpg-Datei mehr. Meine Frage ist also: Wie behalte ich die Originaldatei während der Übertragung? –

+1

Warum brauchen wir das 'setInterval()' Ding? – jrub

2

Zuerst müssen Sie Ihre eigene Inline-TS-Klasse, schaffen, da die Formdata-Klasse ist nicht gut im Moment unterstützt:

var data : { 
    name: string; 
    file: File; 
} = { 
    name: "Name", 
    file: inputValue.files[0] 
    }; 

Sie dann auf den Server mit JSON.stringify senden (Daten)

let opts: RequestOptions = new RequestOptions(); 
opts.method = RequestMethods.Post; 
opts.headers = headers; 
this.http.post(url,JSON.stringify(data),opts); 
+0

https://developer.mozilla.org/en-US/docs/Web/API/FormData#Browser_compatibility Unterstützung ist eigentlich nicht so schlecht. Ich würde mir vorstellen, dass JSON.stringify einige Probleme haben würde, wenn die hochgeladenen Dateien zufällig Binärdateien oder ähnliches wären. – csga5000

4

In meinem Projekt verwende ich die XMLHttpRequest, um Multipart/Form-Daten zu senden. Ich denke, es wird Ihnen passen.

und der uploader Code

let xhr = new XMLHttpRequest(); 
xhr.open('POST', 'http://www.example.com/rest/api', true); 
xhr.withCredentials = true; 
xhr.send(formData); 

Hier ist Beispiel: https://github.com/wangzilong/angular2-multipartForm

+1

Es ist keine gute Antwort, nur auf Ihre eigene Bibliothek (oder Ihr Dienstprogramm) zu verlinken. Verlinkung, Erklärung, warum es das Problem löst, Bereitstellung von Code, um es zu tun, und Verzicht darauf, ist eine bessere Antwort. Siehe: [Wie kann ich auf eine Community-freundliche Weise mit einer externen Ressource verlinken?] (// meta.stackexchange.com/questions/94022/) – Tunaki

23

auf diese Frage sucht Github - Request/Upload progress handling via @angular/http, angular2 http noch nicht Datei-Upload unterstützen.

Für sehr einfache Datei-Upload ich eine solche Service-Funktion als Problemumgehung erstellt (mit Тимофей's answer):

uploadFile(file:File):Promise<MyEntity> { 
    return new Promise((resolve, reject) => { 

     let xhr:XMLHttpRequest = new XMLHttpRequest(); 
     xhr.onreadystatechange =() => { 
      if (xhr.readyState === 4) { 
       if (xhr.status === 200) { 
        resolve(<MyEntity>JSON.parse(xhr.response)); 
       } else { 
        reject(xhr.response); 
       } 
      } 
     }; 

     xhr.open('POST', this.getServiceUrl(), true); 

     let formData = new FormData(); 
     formData.append("file", file, file.name); 
     xhr.send(formData); 
    }); 
} 
+1

Großartig! Damit es funktioniert, musste ich 'xhr.setRequestHeader ('X-Requested-With', 'XMLHttpRequest');'. – patad

+2

Danke! Wenn eine Autorisierung erforderlich ist, fügen Sie diese hinzu, nachdem die xhr.open-Zeile für mich funktioniert hat: 'xhr.setRequestHeader ('Authorization', 'Bearer' + __your_auth_key __);' – Steve

+0

Wird dies jetzt unterstützt? –

8

Ihre http Service-Datei:

import { Injectable } from "@angular/core"; 
import { ActivatedRoute, Router } from '@angular/router'; 
import { Http, Headers, Response, Request, RequestMethod, URLSearchParams, RequestOptions } from "@angular/http"; 
import {Observable} from 'rxjs/Rx'; 
import { Constants } from './constants'; 
declare var $: any; 

@Injectable() 
export class HttpClient { 
    requestUrl: string; 
    responseData: any; 
    handleError: any; 

    constructor(private router: Router, 
    private http: Http, 
    private constants: Constants, 
) { 
    this.http = http; 
    } 

    postWithFile (url: string, postData: any, files: File[]) { 

    let headers = new Headers(); 
    let formData:FormData = new FormData(); 
    formData.append('files', files[0], files[0].name); 
    // For multiple files 
    // for (let i = 0; i < files.length; i++) { 
    //  formData.append(`files[]`, files[i], files[i].name); 
    // } 

    if(postData !=="" && postData !== undefined && postData !==null){ 
     for (var property in postData) { 
      if (postData.hasOwnProperty(property)) { 
       formData.append(property, postData[property]); 
      } 
     } 
    } 
    var returnReponse = new Promise((resolve, reject) => { 
     this.http.post(this.constants.root_dir + url, formData, { 
     headers: headers 
     }).subscribe(
      res => { 
      this.responseData = res.json(); 
      resolve(this.responseData); 
      }, 
      error => { 
      this.router.navigate(['/login']); 
      reject(error); 
      } 
    ); 
    }); 
    return returnReponse; 
    } 
} 

rufen Sie Ihre Funktion (Component-Datei):

onChange(event) { 
    let file = event.srcElement.files; 
    let postData = {field1:"field1", field2:"field2"}; // Put your form data variable. This is only example. 
    this._service.postWithFile(this.baseUrl + "add-update",postData,file).then(result => { 
     console.log(result); 
    }); 
} 

Ihre HTML-Code:

<input type="file" class="form-control" name="documents" (change)="onChange($event)" [(ngModel)]="stock.documents" #documents="ngModel"> 
+0

Eine der einzigen Antworten, die tatsächlich Winkel verwendet – csga5000

+0

Wie kann ich das beheben? Fehler TS2339: Eigenschaft 'Dateien' existiert nicht auf Typ 'Element'. Danke –

+0

'onChange() {...}' sollte 'onChange sein (event) {...} 'wird den Fehler beheben: Property-Dateien existieren nicht;) –