AngularJS: ¿cómo implementar una carga de archivo simple con forma multiparte?

144

Quiero hacer una simple publicación de formulario multiparte desde AngularJS a un servidor node.js, el formulario debe contener un objeto JSON en una parte y una imagen en la otra parte (actualmente estoy publicando solo el objeto JSON con $ resource)

Pensé que debería comenzar con input type = "file", pero luego descubrí que AngularJS no puede unirse a eso ...

Todos los ejemplos que puedo encontrar son para envolver complementos jQuery para arrastrar y soltar. Quiero una carga simple de un archivo.

Soy nuevo en AngularJS y no me siento cómodo con escribir mis propias directivas.

Gal Ben-Haim
fuente
1
Creo que esto podría ayudar: noypi-linux.blogspot.com/2013/04/…
Noypi Gilas
1
Vea esta respuesta: stackoverflow.com/questions/18571001/… Hay muchas opciones disponibles para sistemas que ya funcionan.
Anoyz
1
Ver aquí
bobobobo

Respuestas:

188

Una solución de trabajo real sin otras dependencias que angularjs (probado con v.1.0.6)

html

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

Angularjs (1.0.6) no es compatible con ng-model en las etiquetas "input-file", por lo que debe hacerlo de forma "nativa" que pase todos los archivos (eventualmente) seleccionados por el usuario.

controlador

$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 interesante es el tipo de contenido indefinido y el transformRequest: angular.identity que le da a $ http la capacidad de elegir el "tipo de contenido" correcto y administrar el límite necesario cuando se manejan datos de varias partes.

Fabio Bonfante
fuente
2
@Fabio ¿Puedes explicarme qué hace esta transformRequest? ¿Cuál es la identidad angular? Estuve golpeando mi cabeza durante todo el día solo para lograr la carga de archivos de varias partes.
agpt
1
@RandomUser en una aplicación de descanso de Java, algo así mkyong.com/webservices/jax-rs/file-upload-example-in-jersey
Fabio Bonfante
2
wow, simplemente genial, muchas gracias. Aquí tengo que subir varias imágenes y algunos otros datos, así que manipulo fd comofd.append("file", files[0]); fd.append("file",files[1]); fd.append("name", "MyName")
Moshii
1
console.log (fd) muestra que el formulario está vacío? es asi?
J Bourne
44
Tenga en cuenta que esto no funcionará si tiene debugInfo desactivado (como se recomienda)
Bruno Peres
42

Puede usar la directiva ng-file-upload simple / ligera . Admite arrastrar y soltar, progreso de archivos y carga de archivos para navegadores que no sean HTML5 con Flash Flash de 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
fuente
¿No se trata de realizar una única solicitud POST para cada archivo a la vez?
Anoyz
Si lo hace Hay discusiones en el rastreador de problemas de github sobre por qué es mejor cargar los archivos uno por uno. Flash API no admite el envío de archivos juntos y AFAIK Amazon S3 tampoco lo admite.
danial
Entonces, ¿estás diciendo que el enfoque más correcto en general es enviar una solicitud POST de archivo a la vez? Puedo ver varias ventajas en eso, pero también más problemas al hacer un soporte reparador del lado del servidor.
Anoyz
2
La forma en que lo implemento es cargar cada archivo como un recurso guardado y el servidor lo guardará en el sistema de archivos local (o base de datos) y devolverá una identificación única (es decir, una carpeta / nombre de archivo aleatorio o una identificación de db) para ese archivo. Luego, una vez que se realizan todas las cargas, el cliente envía otra solicitud PUT / POST con datos e identificadores adicionales de los archivos que se cargan para esta solicitud. Entonces el servidor guardaría el registro con los archivos asociados. Es como gmail cuando carga archivos y luego envía el correo electrónico.
danial
1
Esto no es "simple / ligero". La sección de muestras ni siquiera tiene un ejemplo de carga de un solo archivo.
Chris
9

Es más eficiente enviar un archivo directamente.

La codificación de base64Content-Type: multipart/form-data agrega una sobrecarga adicional del 33%. Si el servidor lo admite, es más eficiente enviar los archivos directamente:

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

Al enviar una POST con un objeto File , es importante configurarlo 'Content-Type': undefined. El método de envío XHR detectará el objeto Archivo y establecerá automáticamente el tipo de contenido.

Para enviar múltiples archivos, vea Hacer múltiples $http.postsolicitudes directamente desde una FileList


Pensé que debería comenzar con input type = "file", pero luego descubrí que AngularJS no puede unirse a eso ...

El <input type=file>elemento no funciona por defecto con la directiva ng-model . Necesita una directiva personalizada :

Demostración de trabajo de la directiva "select-ng-files" que funciona 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 de contenido multipart/form-data

Si uno debe enviar 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);
};

Al enviar un POST con la API FormData , es importante establecerlo 'Content-Type': undefined. El método de envío XHR detectará el FormDataobjeto y establecerá automáticamente el encabezado del tipo de contenido en multipart / form-data con el límite apropiado .

georgeawg
fuente
La filesInputdirectiva no parece estar funcionando con Angular 1.6.7, puedo ver los archivos en elng-repeat pero el mismo $scope.variableestá en blanco en el controlador? También uno de sus ejemplos de uso file-modely unofiles-input
Dan
@Dan lo probé y funciona. Si tiene un problema con su código, debe hacer una nueva pregunta con un ejemplo mínimo, completo y verificable . Cambió el nombre de la directiva a select-ng-files. Probado con AngularJS 1.7.2.
georgeawg
5

Acabo de tener este problema. Entonces hay algunos enfoques. El primero es que los nuevos navegadores admiten

var formData = new FormData();

Siga este enlace a un blog con información sobre cómo el soporte se limita a los navegadores modernos, pero de lo contrario resuelve totalmente este problema.

De lo contrario, puede publicar el formulario en un iframe utilizando el atributo de destino. Cuando publique el formulario, asegúrese de establecer el objetivo en un iframe con su propiedad de visualización establecida en none. El objetivo es el nombre del iframe. (Solo para que sepas.)

espero que esto ayude

Edgar Martinez
fuente
AFAIK FormData no funciona con IE. tal vez es una mejor idea hacer codificación base64 del archivo de imagen y enviarlo en JSON? ¿Cómo puedo vincularme a un tipo de entrada = "archivo" con AngularJS para obtener la ruta del archivo elegido?
Gal Ben-Haim
3

Sé que es una entrada tardía, pero he creado una directiva de carga simple. ¡Lo que puedes conseguir trabajando en poco tiempo!

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

ng-simple-upload más en Github con un ejemplo usando API web.

shammelburg
fuente
2
Para ser sincero, sugerir copiar y pegar el código en el archivo Léame del proyecto puede ser una gran marca negra. intente integrar su proyecto con administradores de paquetes comunes como npm o bower.
Stefano Torresi
2

Puede cargar mediante la $resourceasignación de datos al atributo params del recurso de la siguiente actionsmanera:

$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
fuente
1

Acabo de escribir una directiva simple (del curso existente) para un cargador simple en AngularJs.

(El complemento exacto del cargador de jQuery es https://github.com/blueimp/jQuery-File-Upload )

Un cargador simple que usa AngularJs (con implementación CORS)

(Aunque el lado del servidor es para PHP, también puede cambiarlo de nodo)

sk8terboi87 ツ
fuente
1
No olvide mencionar que no da tiempo para cerrar \ responder a los problemas en su repositorio
Oleg Belousov
@OlegTikhonov: Sí, en serio atascado con otros proyectos. Apenas capaz de concentrarse.
sk8terboi87 ツ