AngularJS: comment implémenter un simple téléchargement de fichier avec un formulaire en plusieurs parties?

144

Je veux faire une simple publication de formulaire en plusieurs parties d'AngularJS vers un serveur node.js, le formulaire doit contenir un objet JSON dans une partie et une image dans l'autre partie, (je publie actuellement uniquement l'objet JSON avec $ resource)

J'ai pensé que je devrais commencer par input type = "file", mais j'ai ensuite découvert qu'AngularJS ne pouvait pas se lier à cela.

tous les exemples que je peux trouver sont pour envelopper des plugins jQuery pour le glisser-déposer. Je veux un simple téléchargement d'un fichier.

Je suis nouveau sur AngularJS et je ne me sens pas du tout à l'aise avec l'écriture de mes propres directives.

Gal Ben-Haim
la source
1
je pense que cela pourrait aider: noypi-linux.blogspot.com/2013/04/…
Noypi Gilas
1
Voir cette réponse: stackoverflow.com/questions/18571001/... Beaucoup d'options là-bas pour les systèmes déjà fonctionnels.
Anoyz
1
Voir ici
bobobobo

Réponses:

188

Une vraie solution de travail sans autres dépendances que angularjs (testé avec la v.1.0.6)

html

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

Angularjs (1.0.6) ne prend pas en charge ng-model sur les balises "input-file" donc vous devez le faire de manière "native" qui transmet tous les fichiers (éventuellement) sélectionnés par l'utilisateur.

manette

$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 partie intéressante est le type de contenu indéfini et le transformRequest: angular.identity qui donnent au $ http la possibilité de choisir le bon "type de contenu" et de gérer la limite nécessaire lors de la gestion de données en plusieurs parties.

Fabio Bonfante
la source
2
@Fabio Pouvez-vous m'expliquer ce que fait cette transformRequest? qu'est-ce que l'identité angulaire? Je me cognais la tête toute la journée juste pour effectuer le téléchargement de fichiers en plusieurs parties.
agpt le
1
@RandomUser dans une application de repos Java, quelque chose comme ça mkyong.com/webservices/jax-rs/file-upload-example-in-jersey
Fabio Bonfante
2
wow, tout simplement génial, merci beaucoup. Ici, je dois télécharger plusieurs images et d'autres données, donc je manipule fd asfd.append("file", files[0]); fd.append("file",files[1]); fd.append("name", "MyName")
Moshii
1
console.log (fd) indique que le formulaire est vide? est-ce ainsi?
J Bourne
4
Notez que cela ne fonctionnera pas si vous avez désactivé debugInfo (comme recommandé)
Bruno Peres
42

Vous pouvez utiliser la directive simple / légère ng-file-upload . Il prend en charge le glisser-déposer, la progression du fichier et le téléchargement de fichiers pour les navigateurs non HTML5 avec le 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);
  }); 

}];
danial
la source
N'est-ce pas effectuer une seule requête POST pour chaque fichier à la fois?
Anoyz
Oui. Il y a des discussions dans le suivi des problèmes de github sur les raisons pour lesquelles il est préférable de télécharger les fichiers un par un. L'API Flash ne prend pas en charge l'envoi de fichiers ensemble et AFAIK Amazon S3 ne le prend pas non plus en charge.
danial
Donc, vous dites que l'approche générale la plus correcte consiste à envoyer une demande POST de fichier à la fois? Je peux voir plusieurs avantages à cela, mais aussi plus de problèmes lors de la création d'un support reposant côté serveur.
Anoyz
2
La façon dont je l'implémente est de télécharger chaque fichier en tant que sauvegarde de ressources et le serveur l'enregistrera sur le système de fichiers local (ou la base de données) et retournera un identifiant unique (c.-à-d. Nom de dossier / fichier aléatoire ou id de base de données) pour ce fichier. Ensuite, une fois que tous les téléchargements sont terminés, le client envoie une autre demande PUT / POST contenant les données et les identifiants supplémentaires des fichiers téléchargés pour cette demande. Ensuite, le serveur enregistre l'enregistrement avec les fichiers associés. C'est comme gmail lorsque vous téléchargez des fichiers, puis envoyez l'e-mail.
danial
1
Ce n'est pas "simple / léger". La section des échantillons n'a même pas d'exemple de téléchargement d'un seul fichier.
Chris
9

Il est plus efficace d'envoyer un fichier directement.

L' encodage base64 de Content-Type: multipart/form-dataajoute une surcharge supplémentaire de 33%. Si le serveur le prend en charge, il est plus efficace d'envoyer directement les fichiers:

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

Lors de l'envoi d'un POST avec un objet File , il est important de définir 'Content-Type': undefined. La méthode d'envoi XHR détectera alors l' objet File et définira automatiquement le type de contenu.

Pour envoyer plusieurs fichiers, consultez Faire plusieurs $http.postrequêtes directement à partir d'une FileList


J'ai pensé que je devrais commencer par input type = "file", mais j'ai ensuite découvert qu'AngularJS ne pouvait pas se lier à cela.

L' <input type=file>élément ne fonctionne pas par défaut avec la directive ng-model . Il a besoin d'une directive personnalisée :

Démonstration fonctionnelle de la directive "select-ng-files" qui fonctionne avec 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 avec le type de contenu multipart/form-data

S'il faut envoyer 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);
};

Lors de l'envoi d'un POST avec l' API FormData , il est important de définir 'Content-Type': undefined. La méthode d'envoi XHR détectera alors l' FormDataobjet et définira automatiquement l'en-tête de type de contenu sur multipart / form-data avec la limite appropriée .

georgeawg
la source
La filesInputdirective ne semble pas fonctionner avec Angular 1.6.7, je peux voir les fichiers dans le ng-repeatmais le même $scope.variableest vide dans le contrôleur? Aussi l'un de vos exemples utilise file-modelet unfiles-input
Dan
@Dan je l'ai testé et ça marche. Si vous rencontrez un problème avec votre code, vous devez poser une nouvelle question avec un exemple minimal, complet et vérifiable . Le nom de la directive a été remplacé par select-ng-files. Testé avec AngularJS 1.7.2.
georgeawg
5

J'ai juste eu ce problème. Il y a donc quelques approches. Le premier est que les nouveaux navigateurs prennent en charge le

var formData = new FormData();

Suivez ce lien vers un blog avec des informations sur la façon dont le support est limité aux navigateurs modernes, mais sinon, cela résout totalement ce problème.

Sinon, vous pouvez publier le formulaire dans une iframe à l'aide de l'attribut cible. Lorsque vous publiez le formulaire, assurez-vous de définir la cible sur un iframe avec sa propriété d'affichage définie sur aucun. La cible est le nom de l'iframe. (Juste pour que vous le sachiez.)

J'espère que ça aide

Edgar Martinez
la source
AFAIK FormData ne fonctionne pas avec IE. peut-être que c'est une meilleure idée de faire l'encodage base64 du fichier image et de l'envoyer en JSON? comment puis-je lier à un type d'entrée = "fichier" avec AngularJS pour obtenir le chemin de fichier choisi?
Gal Ben-Haim
3

Je sais que c'est une entrée tardive mais j'ai créé une simple directive de téléchargement. Ce qui vous permet de travailler en un rien de temps!

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

ng-simple-upload plus sur Github avec un exemple utilisant l'API Web.

Shammelburg
la source
2
pour être honnête, suggérer de copier-coller du code dans le fichier readme de votre projet peut être une grosse marque noire. essayez d'intégrer votre projet avec des gestionnaires de packages courants tels que npm ou bower.
Stefano Torresi
2

Vous pouvez télécharger via $resourceen attribuant des données à l'attribut params de la ressource actionscomme ceci:

$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
la source
1

Je viens d'écrire une directive simple (à partir d'une directive existante bien sûr) pour un simple uploader dans AngularJs.

(Le plugin exact de téléchargement de jQuery est https://github.com/blueimp/jQuery-File-Upload )

Un téléchargeur simple utilisant AngularJs (avec implémentation CORS)

(Bien que le côté serveur soit pour PHP, vous pouvez simplement le changer de nœud également)

sk8terboi87 ツ
la source
1
N'oubliez pas de mentionner que vous ne donnez pas le temps de fermer \ répondre aux problèmes de votre repo
Oleg Belousov
@OlegTikhonov: Oui, sérieusement coincé avec d'autres projets. A peine capable de se concentrer.
sk8terboi87 ツ