AngularJS: come implementare un semplice caricamento di file con modulo multipart?


144

Voglio fare un semplice post modulo multipart da AngularJS a un server node.js, il modulo dovrebbe contenere un oggetto JSON in una parte e un'immagine nell'altra parte (al momento sto pubblicando solo l'oggetto JSON con $ risorsa)

Ho pensato che avrei dovuto iniziare con input type = "file", ma poi ho scoperto che AngularJS non può legarsi a quello ...

tutti gli esempi che posso trovare sono per avvolgere i plugin jQuery per il trascinamento della selezione. Voglio un semplice caricamento di un file.

Sono nuovo di AngularJS e non mi sento affatto a mio agio nello scrivere le mie direttive.


1
penso che questo potrebbe aiutare: noypi-linux.blogspot.com/2013/04/…
Noypi Gilas,

1
Vedi questa risposta: stackoverflow.com/questions/18571001/… Molte opzioni lì per sistemi già funzionanti.
Anoyz,

Risposte:


188

Una vera soluzione funzionante senza altre dipendenze da angularjs (testata con v.1.0.6)

html

<input type="file" name="file" onchange="angular.element(this).scope().uploadFile(this.files)"/>

Angularjs (1.0.6) non supporta ng-model su tag "input-file", quindi è necessario farlo in un "modo nativo" che passi tutti i file (eventualmente) selezionati dall'utente.

controllore

$scope.uploadFile = function(files) {
    var fd = new FormData();
    //Take the first selected file
    fd.append("file", files[0]);

    $http.post(uploadUrl, fd, {
        withCredentials: true,
        headers: {'Content-Type': undefined },
        transformRequest: angular.identity
    }).success( ...all right!... ).error( ..damn!... );

};

La parte interessante è il tipo di contenuto indefinito e il transformRequest: angular.identity che dà a $ http la possibilità di scegliere il giusto "tipo di contenuto" e gestire il confine necessario quando si gestiscono dati multipart.


2
@Fabio Puoi spiegarmi cosa fa questo TransformRequest? cos'è l'angular.identity? Stavo sbattendo la testa per tutto il giorno solo per realizzare il caricamento di file multipart.
agpt

1
@RandomUser in un'applicazione java rest, qualcosa del genere mkyong.com/webservices/jax-rs/file-upload-example-in-jersey
Fabio Bonfante

2
wow, semplicemente geniale, grazie mille. Qui devo caricare più immagini e alcuni altri dati, quindi manipolo fd comefd.append("file", files[0]); fd.append("file",files[1]); fd.append("name", "MyName")
Moshii,

1
console.log (fd) mostra che il modulo è vuoto? è così?
J Bourne,

4
Nota che questo non funzionerà se debugInfo è disabilitato (come raccomandato)
Bruno Peres

42

Puoi usare la direttiva ng-file-upload semplice / leggera . Supporta il trascinamento della selezione, l'avanzamento e il caricamento dei file per browser non HTML5 con shim flash FileAPI

<div ng-controller="MyCtrl">
  <input type="file" ngf-select="onFileSelect($files)" multiple>
</div>

JS:

//inject angular file upload directive.
angular.module('myApp', ['ngFileUpload']);

var MyCtrl = [ '$scope', 'Upload', function($scope, Upload) {
  $scope.onFileSelect = function($files) {
  Upload.upload({
    url: 'my/upload/url',
    file: $files,            
  }).progress(function(e) {
  }).then(function(data, status, headers, config) {
    // file is uploaded successfully
    console.log(data);
  }); 

}];

Questo non sta eseguendo una singola richiesta POST per ciascun file alla volta?
Anoyz,

Sì lo fa. Ci sono discussioni nel tracker del problema github sul perché è meglio caricare i file uno per uno. L'API Flash non supporta l'invio di file insieme e nemmeno AFAIK Amazon S3.
danial

Quindi, stai dicendo che l'approccio più corretto generale è l'invio di una richiesta POST di file alla volta? Vedo numerosi vantaggi in questo, ma anche più problemi quando si fornisce supporto riposante sul lato server.
Anoyz,

2
Il modo in cui lo implemento è caricare ciascun file come salvataggio delle risorse e il server lo salverà sul file system locale (o sul database) e restituirà un ID univoco (ovvero nome cartella / file casuale o ID db) per quel file. Quindi, una volta terminati tutti i caricamenti, il client invia un'altra richiesta PUT / POST quali dati e ID aggiuntivi dei file caricati per questa richiesta. Quindi il server salverà il record con i file associati. È come gmail quando carichi i file e poi invii l'email.
danial

1
Questo non è "semplice / leggero". La sezione degli esempi non ha nemmeno un esempio di caricamento di un solo file.
Chris,

9

È più efficiente inviare un file direttamente.

La codifica base64 di Content-Type: multipart/form-dataaggiunge un sovraccarico del 33% in più. Se il server lo supporta, è più efficiente inviare i file direttamente:

$scope.upload = function(url, file) {
    var config = { headers: { 'Content-Type': undefined },
                   transformResponse: angular.identity
                 };
    return $http.post(url, file, config);
};

Quando si invia un POST con un oggetto File , è importante impostare 'Content-Type': undefined. Il metodo di invio XHR rileverà quindi l' oggetto File e imposterà automaticamente il tipo di contenuto.

Per inviare più file, vedere Esecuzione di più $http.postrichieste direttamente da un Elenco file


Ho pensato che avrei dovuto iniziare con input type = "file", ma poi ho scoperto che AngularJS non può legarsi a quello ...

L' <input type=file>elemento non funziona per impostazione predefinita con la direttiva ng-model . Ha bisogno di una direttiva personalizzata :

Demo funzionante della direttiva "select-ng-files" che funziona con ng-model1

angular.module("app",[]);

angular.module("app").directive("selectNgFiles", function() {
  return {
    require: "ngModel",
    link: function postLink(scope,elem,attrs,ngModel) {
      elem.on("change", function(e) {
        var files = elem[0].files;
        ngModel.$setViewValue(files);
      })
    }
  }
});
<script src="//unpkg.com/angular/angular.js"></script>
  <body ng-app="app">
    <h1>AngularJS Input `type=file` Demo</h1>
    
    <input type="file" select-ng-files ng-model="fileArray" multiple>
    
    <h2>Files</h2>
    <div ng-repeat="file in fileArray">
      {{file.name}}
    </div>
  </body>


$http.post con tipo di contenuto multipart/form-data

Se uno deve inviare multipart/form-data:

<form role="form" enctype="multipart/form-data" name="myForm">
    <input type="text"  ng-model="fdata.UserName">
    <input type="text"  ng-model="fdata.FirstName">
    <input type="file"  select-ng-files ng-model="filesArray" multiple>
    <button type="submit" ng-click="upload()">save</button>
</form>
$scope.upload = function() {
    var fd = new FormData();
    fd.append("data", angular.toJson($scope.fdata));
    for (i=0; i<$scope.filesArray.length; i++) {
        fd.append("file"+i, $scope.filesArray[i]);
    };

    var config = { headers: {'Content-Type': undefined},
                   transformRequest: angular.identity
                 }
    return $http.post(url, fd, config);
};

Quando si invia un POST con l' API FormData , è importante impostare 'Content-Type': undefined. Il metodo di invio XHR rileverà quindi l' FormDataoggetto e imposterà automaticamente l'intestazione del tipo di contenuto su multipart / form-data con il limite appropriato .


La filesInputdirettiva non sembra funzionare con Angular 1.6.7, posso vedere i file nel ng-repeatma lo stesso $scope.variableè vuoto nel controller? Anche uno dei tuoi esempi usa file-modele unofiles-input
Dan,

@Dan L'ho provato e funziona. In caso di problemi con il codice, è necessario porre una nuova domanda con un esempio minimo, completo e verificabile . Modificato il nome della direttiva in select-ng-files. Testato con AngularJS 1.7.2.
georgeawg,

5

Ho appena avuto questo problema. Quindi ci sono alcuni approcci. Il primo è che i nuovi browser supportano il

var formData = new FormData();

Segui questo link a un blog con informazioni su come il supporto è limitato ai browser moderni, ma per il resto risolve completamente questo problema.

Altrimenti puoi inviare il modulo a un iframe usando l'attributo target. Quando pubblichi il modulo, assicurati di impostare la destinazione su un iframe con la proprietà display impostata su none. La destinazione è il nome dell'iframe. (Solo così lo sai.)

Spero che aiuti


AFAIK FormData non funziona con IE. forse è un'idea migliore fare la codifica base64 del file immagine e inviarlo in JSON? come posso associare un input type = "file" con AngularJS per ottenere il percorso del file scelto?
Gal Ben-Haim,

3

So che questa è una voce in ritardo, ma ho creato una semplice direttiva di upload. Che puoi iniziare a lavorare in pochissimo tempo!

<input type="file" multiple ng-simple-upload web-api-url="/api/post"
       callback-fn="myCallback" />

ng-simple-upload di più su Github con un esempio usando l'API Web.


2
a dire il vero, suggerire di copiare e incollare il codice nel file Leggimi del progetto può essere un grande segno nero. prova a integrare il tuo progetto con gestori di pacchetti comuni come npm o bower.
Stefano Torresi,

2

È possibile caricare tramite $resourceassegnando i dati all'attributo params della risorsa in questo actionsmodo:

$scope.uploadFile = function(files) {
    var fdata = new FormData();
    fdata.append("file", files[0]);

    $resource('api/post/:id', { id: "@id" }, {
        postWithFile: {
            method: "POST",
            data: fdata,
            transformRequest: angular.identity,
            headers: { 'Content-Type': undefined }
        }
    }).postWithFile(fdata).$promise.then(function(response){
         //successful 
    },function(error){
        //error
    });
};

1

Ho appena scritto una semplice direttiva (da quella esistente) per un semplice uploader in AngularJs.

(Il plug-in per il caricamento esatto di jQuery è https://github.com/blueimp/jQuery-File-Upload )

Un semplice Uploader che utilizza AngularJs (con implementazione CORS)

(Anche se il lato server è per PHP, puoi anche semplicemente cambiarlo nodo)


1
Non dimenticare di dire che non dai il tempo di chiudere \ rispondere ai problemi sul tuo repository
Oleg Belousov,

@OlegTikhonov: Sì, seriamente bloccato con alcuni altri progetti. Difficilmente in grado di mettere a fuoco.
sk8terboi87 ツ
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.