AngularJS: Wie implementiere ich einen einfachen Datei-Upload mit mehrteiliger Form?

144

Ich möchte einen einfachen mehrteiligen Formularbeitrag von AngularJS auf einen node.js-Server erstellen. Das Formular sollte ein JSON-Objekt in einem Teil und ein Bild im anderen Teil enthalten (ich veröffentliche derzeit nur das JSON-Objekt mit $ resource).

Ich dachte, ich sollte mit der Eingabe type = "file" beginnen, fand dann aber heraus, dass AngularJS sich nicht daran binden kann.

Alle Beispiele, die ich finden kann, sind zum Umschließen von jQuery-Plugins für Drag & Drop. Ich möchte einen einfachen Upload einer Datei.

Ich bin neu bei AngularJS und fühle mich überhaupt nicht wohl, wenn ich meine eigenen Anweisungen schreibe.

Gal Ben-Haim
quelle
1
Ich denke, das könnte helfen: noypi-linux.blogspot.com/2013/04/…
Noypi Gilas
1
Siehe diese Antwort: stackoverflow.com/questions/18571001/… Viele Optionen für bereits funktionierende Systeme.
Anoyz
1
Siehe hier
Bobobobo

Antworten:

188

Eine echte funktionierende Lösung ohne andere Abhängigkeiten als anglejs (getestet mit v.1.0.6)

html

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

Angularjs (1.0.6) unterstützt ng-model für "Eingabedatei" -Tags nicht, daher müssen Sie dies auf "native Weise" tun, wobei alle (eventuell) ausgewählten Dateien vom Benutzer übergeben werden.

Regler

$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!... );

};

Der coole Teil ist der undefinierte Inhaltstyp und die transformRequest: angle.identity , die am $ http die Möglichkeit geben, den richtigen "Inhaltstyp" auszuwählen und die Grenze zu verwalten, die beim Umgang mit mehrteiligen Daten erforderlich ist.

Fabio Bonfante
quelle
2
@Fabio Kannst du mir bitte erklären, was diese transformRequest macht? Was ist die eckige Identität? Ich habe den ganzen Tag mit dem Kopf geschlagen, nur um den mehrteiligen Datei-Upload durchzuführen.
agpt
1
@RandomUser in einer Java-Rest-Anwendung, so etwas wie das mkyong.com/webservices/jax-rs/file-upload-example-in-jersey
Fabio Bonfante
2
Wow, einfach genial, vielen Dank. Hier muss ich mehrere Bilder und einige andere Daten hochladen, damit ich fd asfd.append("file", files[0]); fd.append("file",files[1]); fd.append("name", "MyName")
Moshii
1
console.log (fd) zeigt an, dass das Formular leer ist? ist es so
J Bourne
4
Beachten Sie, dass dies nicht funktioniert, wenn Sie debugInfo deaktiviert haben (wie empfohlen)
Bruno Peres
42

Sie können die einfache / leichte Direktive zum Hochladen von ng-Dateien verwenden . Es unterstützt Drag & Drop, Dateifortschritt und Datei-Upload für Nicht-HTML5-Browser mit FileAPI Flash Shim

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

}];
danial
quelle
Führt dies nicht eine einzelne POST-Anforderung für jede Datei gleichzeitig aus?
Anoyz
Ja tut es. Im Github Issue Tracker gibt es Diskussionen darüber, warum es besser ist, die Dateien einzeln hochzuladen. Die Flash-API unterstützt das gemeinsame Senden von Dateien nicht und AFAIK Amazon S3 unterstützt dies auch nicht.
Danial
Sie sagen also, dass der allgemein korrektere Ansatz darin besteht, jeweils eine POST-Dateianforderung zu senden? Darin sehe ich mehrere Vorteile, aber auch mehr Probleme bei der serverseitigen erholsamen Unterstützung.
Anoyz
2
Ich implementiere es so, dass jede Datei als Ressourcenspeicher hochgeladen wird. Der Server speichert sie im lokalen Dateisystem (oder in der Datenbank) und gibt eine eindeutige ID (dh einen zufälligen Ordner- / Dateinamen oder eine Datenbank-ID) für diese Datei zurück. Sobald alle Uploads abgeschlossen sind, sendet der Client eine weitere PUT / POST-Anforderung, die zusätzliche Daten und IDs der Dateien enthält, die für diese Anforderung hochgeladen wurden. Dann würde der Server den Datensatz mit den zugehörigen Dateien speichern. Es ist wie bei Google Mail, wenn Sie Dateien hochladen und dann die E-Mail senden.
Danial
1
Dies ist nicht "einfach / leicht". Der Beispielabschnitt enthält nicht einmal ein Beispiel für das Hochladen nur einer Datei.
Chris
9

Es ist effizienter, eine Datei direkt zu senden.

Die Base64-Codierung von Content-Type: multipart/form-dataerhöht den Overhead um 33%. Wenn der Server dies unterstützt, ist es effizienter, die Dateien direkt zu senden:

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

Beim Senden eines POST mit einem Dateiobjekt ist es wichtig, dies festzulegen 'Content-Type': undefined. Die XHR-Sendemethode erkennt dann das Dateiobjekt und legt den Inhaltstyp automatisch fest.

Informationen zum Senden mehrerer Dateien finden Sie unter Ausführen mehrerer $http.postAnforderungen direkt aus einer Dateiliste


Ich dachte, ich sollte mit der Eingabe type = "file" beginnen, fand dann aber heraus, dass AngularJS sich nicht daran binden kann.

Das <input type=file>Element funktioniert standardmäßig nicht mit der ng-model-Direktive . Es braucht eine benutzerdefinierte Anweisung :

Arbeitsdemo der Direktive "select-ng-files", die mit 1 funktioniertng-model

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 mit Inhaltstyp multipart/form-data

Wenn man senden muss 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);
};

Beim Senden eines POST mit der FormData-API ist es wichtig, diese festzulegen'Content-Type': undefined . Die XHR-Sendemethode erkennt dann das FormDataObjekt und setzt den Inhaltstyp-Header automatisch auf Multipart- / Formulardaten mit der richtigen Grenze .

georgeawg
quelle
Die filesInputDirektive scheint nicht mit Angular 1.6.7 zu funktionieren. Ich kann die Dateien im sehen, ng-repeataber das gleiche $scope.variableist im Controller leer. Auch eines Ihrer Beispiele verwendet file-modelund einesfiles-input
Dan
@ Dan Ich habe es getestet und es funktioniert. Wenn Sie ein Problem mit Ihrem Code haben, sollten Sie eine neue Frage mit einem minimalen, vollständigen und überprüfbaren Beispiel stellen . Der Direktivenname wurde in geändert select-ng-files. Getestet mit AngularJS 1.7.2.
Georgiaeawg
5

Ich hatte gerade dieses Problem. Es gibt also einige Ansätze. Das erste ist, dass neue Browser das unterstützen

var formData = new FormData();

Folgen Sie diesem Link zu einem Blog mit Informationen darüber, wie die Unterstützung auf moderne Browser beschränkt ist. Andernfalls wird dieses Problem vollständig gelöst.

Andernfalls können Sie das Formular mithilfe des Zielattributs an einen Iframe senden. Stellen Sie beim Posten des Formulars sicher, dass das Ziel auf einen Iframe festgelegt ist, dessen Anzeigeeigenschaft auf none festgelegt ist. Das Ziel ist der Name des Iframes. (Nur damit du es weißt.)

ich hoffe das hilft

Edgar Martinez
quelle
AFAIK FormData funktioniert nicht mit IE. Vielleicht ist es eine bessere Idee, die Image-Datei mit Base64 zu codieren und in JSON zu senden? Wie kann ich mit AngularJS an einen Eingabetyp = "Datei" binden, um den ausgewählten Dateipfad zu erhalten?
Gal Ben-Haim
3

Ich weiß, dass dies ein verspäteter Eintrag ist, aber ich habe eine einfache Upload-Direktive erstellt. Was Sie in kürzester Zeit zum Arbeiten bringen können!

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

ng-simple-upload mehr auf Github mit einem Beispiel mit Web-API.

Shammelburg
quelle
2
Um ehrlich zu sein, kann der Vorschlag, Code in Ihre Projekt-Readme-Datei zu kopieren und einzufügen, eine große schwarze Markierung sein. Versuchen Sie, Ihr Projekt in gängige Paketmanager wie npm oder bower zu integrieren.
Stefano Torresi
2

Sie können über hochladen, $resourceindem Sie Daten dem Parameterattribut der Ressource actionswie folgt zuweisen :

$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
    });
};
Emeka Mbah
quelle
1

Ich habe gerade eine einfache Direktive (natürlich aus einer bestehenden) für einen einfachen Uploader in AngularJs geschrieben.

(Das genaue Plugin für den jQuery-Uploader lautet https://github.com/blueimp/jQuery-File-Upload )

Ein einfacher Uploader mit AngularJs (mit CORS-Implementierung)

(Obwohl die Serverseite für PHP ist, können Sie den Knoten auch einfach ändern.)

sk8terboi87 ツ
quelle
1
Vergessen Sie nicht zu erwähnen, dass Sie keine Zeit zum Schließen geben, um auf Probleme in Ihrem Repo zu reagieren
Oleg Belousov
@OlegTikhonov: Ja, ich habe mich ernsthaft mit einigen anderen Projekten beschäftigt. Kaum fokussierbar.
sk8terboi87