144 votes

AngularJS : comment mettre en œuvre un simple téléchargement de fichier avec un formulaire multipartite ?

Je veux faire un simple post de formulaire multipart depuis AngularJS vers un serveur node.js, le formulaire doit contenir un objet JSON dans une partie et une image dans l'autre partie, (actuellement, je ne poste que l'objet JSON avec $resource)

Je me suis dit que je devais commencer avec input type="file", mais j'ai découvert qu'AngularJS ne peut pas se lier à ce

Tous les exemples que j'ai pu trouver concernent l'intégration de plugins jQuery pour le glisser-déposer. Je veux un simple téléchargement d'un fichier.

Je suis nouveau dans AngularJS et je ne me sens pas du tout à l'aise pour écrire mes propres directives.

1 votes

Je pense que ça pourrait aider : noypi-linux.blogspot.com/2013/04/

1 votes

Voir cette réponse : stackoverflow.com/questions/18571001/ Il existe de nombreuses options pour les systèmes qui fonctionnent déjà.

1 votes

191voto

Fabio Points 992

Une solution qui fonctionne réellement, sans autre dépendance qu'angularjs (testé avec la version 1.0.6).

html

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

Angularjs (1.0.6) non supporté ng-model sur les balises "input-file", vous devez donc le faire de manière "native" en passant tous les fichiers (éventuellement) sélectionnés par l'utilisateur.

contrôleur

$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 la plus cool est le indéfini et le type de contenu transformRequest : angular.identity qui donnent au $http la possibilité de choisir le bon "content-type" et de gérer la frontière nécessaire à la manipulation de données multipart.

0 votes

C'est très cool, une idée de comment implémenter un fallback IE (pas de support pour l'objet FormData), ou comment inclure d'autres champs de formulaire dans ce multipart ?

0 votes

J'ai une autre implémentation "native js" sans utiliser $http, mais elle nécessite toujours l'objet FormData... si IE ne l'a pas (vraiment ? !! les formulaires existent depuis HTML1.0 ! !!) peut-être que la seule solution est de "simuler" l'objet... essayez de jeter un coup d'oeil à modernizr.com

2 votes

@Fabio Pouvez-vous m'expliquer ce que fait ce transformRequest ? ce qu'est l'angular.identity ? J'ai passé la journée à me casser la tête pour réaliser un téléchargement de fichier multipart.

44voto

danial Points 868

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

}];

0 votes

N'est-ce pas effectuer une seule requête POST pour chaque fichier à la fois ?

0 votes

Oui, c'est vrai. Il y a des discussions dans le traqueur de 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, à priori, Amazon S3 ne le fait pas non plus.

0 votes

Donc, vous dites que l'approche générale la plus correcte est d'envoyer une demande POST de fichier à la fois ? Je vois plusieurs avantages à cela, mais aussi plus de problèmes lors de la mise en place d'un support Restful côté serveur.

11voto

georgeawg Points 31282

Il est plus efficace d'envoyer un fichier directement.

El encodage base64 de Content-Type: multipart/form-data ajoute 33% de frais généraux supplémentaires. Si le serveur le supporte, il est plus efficace d'envoyer les fichiers directement :

$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 du fichier il est important de définir 'Content-Type': undefined . Le site Méthode d'envoi XHR détectera alors le Objet du fichier et définir automatiquement le type de contenu.

Pour envoyer plusieurs fichiers, voir Faire de multiples $http.post Demandes directes à partir d'une liste de fichiers


Je me suis dit que je devais commencer avec input type="file", mais j'ai découvert qu'AngularJS ne peut pas se lier à ce

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

Démonstration fonctionnelle de la directive "select-ng-files" qui fonctionne avec ng-model 1

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

Si l'on doit 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'option API FormData il est important de définir 'Content-Type': undefined . Le site Méthode d'envoi XHR détectera alors le FormData et définit automatiquement l'en-tête du type de contenu comme étant multipart/form-data avec le limite appropriée .

0 votes

Les filesInput ne semble pas fonctionner avec Angular 1.6.7, je peux voir les fichiers dans le répertoire ng-repeat mais la même $scope.variable est vide dans le contrôleur ? Par ailleurs, l'un de vos exemples utilise file-model et un files-input

0 votes

@Dan Je l'ai testé et il fonctionne. Si vous avez un problème avec votre code, vous devriez poser une nouvelle question avec un champ Exemple minimal, complet, vérifiable . Le nom de la directive a été remplacé par select-ng-files . Testé avec AngularJS 1.7.2.

5voto

Edgar Martinez Points 961

Je viens d'avoir ce problème. Il y a donc plusieurs approches. La première est que les nouveaux navigateurs prennent en charge l'option

var formData = new FormData();

Suivez ce lien vers un blog contenant des informations sur La prise en charge est limitée aux navigateurs modernes, mais sinon, elle résout totalement le problème.

Sinon, vous pouvez afficher le formulaire dans une iframe en utilisant l'attribut target. Lorsque vous publiez le formulaire, veillez à définir la cible sur une iframe dont la propriété d'affichage est définie sur none. La cible est le nom de la iframe. (Pour votre information).

J'espère que cela vous aidera

0 votes

AFAIK FormData ne fonctionne pas avec IE. Peut-être est-ce une meilleure idée de faire un encodage base64 du fichier image et de l'envoyer en JSON ? Comment puis-je me lier à une entrée type="file" avec AngularJS pour obtenir le chemin du fichier choisi ?

3voto

Burg Points 233

Je sais que c'est une entrée tardive mais j'ai créé une simple directive de téléchargement. Vous pouvez la faire fonctionner 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.

2 votes

Pour être honnête, suggérer de copier-coller du code dans le readme de votre projet peut être un gros point noir. Essayez d'intégrer votre projet avec des gestionnaires de paquets communs comme npm ou bower.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X