AngularJs: ¿Cómo verificar los cambios en los campos de entrada de archivos?

281

Soy nuevo en angular. Estoy tratando de leer la ruta del archivo cargado desde el campo 'archivo' HTML cada vez que ocurre un 'cambio' en este campo. Si uso 'onChange' funciona, pero cuando lo uso de forma angular usando 'ng-change' no funciona.

<script>
   var DemoModule = angular.module("Demo",[]);
   DemoModule .controller("form-cntlr",function($scope){
   $scope.selectFile = function()
   {
        $("#file").click();
   }
   $scope.fileNameChaged = function()
   {
        alert("select file");
   }
});
</script>

<div ng-controller="form-cntlr">
    <form>
         <button ng-click="selectFile()">Upload Your File</button>
         <input type="file" style="display:none" 
                          id="file" name='file' ng-Change="fileNameChaged()"/>
    </form>  
</div>

fileNameChaged () nunca llama. Firebug tampoco muestra ningún error.

Manish Kumar
fuente
1
Podría ser útil: stackoverflow.com/q/17063000/983992
Marcel Gwerder

Respuestas:

240

No hay soporte vinculante para el control de carga de archivos

https://github.com/angular/angular.js/issues/1375

<div ng-controller="form-cntlr">
        <form>
             <button ng-click="selectFile()">Upload Your File</button>
             <input type="file" style="display:none" 
                id="file" name='file' onchange="angular.element(this).scope().fileNameChanged(this)" />
        </form>  
    </div>

en vez de

 <input type="file" style="display:none" 
    id="file" name='file' ng-Change="fileNameChanged()" />

puedes intentar

<input type="file" style="display:none" 
    id="file" name='file' onchange="angular.element(this).scope().fileNameChanged()" />

Nota: esto requiere que la aplicación angular esté siempre en modo de depuración . Esto no funcionará en el código de producción si el modo de depuración está deshabilitado.

y en su función cambia en lugar de

$scope.fileNameChanged = function() {
   alert("select file");
}

puedes intentar

$scope.fileNameChanged = function() {
  console.log("select file");
}

A continuación se muestra un ejemplo práctico de carga de archivos con la carga de archivos de arrastrar y soltar. Http://jsfiddle.net/danielzen/utp7j/

Información de carga de archivos angular

URL para la carga de archivos AngularJS en ASP.Net

http://cgeers.com/2013/05/03/angularjs-file-upload/

Subida de archivos múltiples nativos de AngularJs con progreso con NodeJS

http://jasonturim.wordpress.com/2013/09/12/angularjs-native-multi-file-upload-with-progress/

ngUpload: un servicio de AngularJS para cargar archivos usando iframe

http://ngmodules.org/modules/ngUpload

JQuery Guru
fuente
2
onchange = "angular.element (this) .scope (). fileNameChaged (this)" $ scope.fileNameChaged = function (element) {console.log ('files:', element.files); } ¿Se puede cambiar en dos lugares y comprobar? Dependiendo del navegador, obtendrá la ruta completa del archivo. He actualizado el violín a continuación, es el archivo de carga de la URL y verifique la consola jsfiddle.net/utp7j/260
JQuery Guru
13
Este método evita el procesamiento predeterminado de AngularJS, por lo que no se llama a $ scope. $ Digest () (los enlaces no se actualizan). Llame 'angular.element (this) .scope (). $ Digest ()' después o llame a un método de alcance que use $ apply.
Ramon de Klein
113
Esta es una respuesta horrible. Llamar a angular.element (blah) .scope () es una función de depuración que solo debe usar para la depuración. Angular no usa la función .scope. Solo está allí para ayudar a los desarrolladores a depurar. Cuando crea su aplicación y la implementa en producción, debe desactivar la información de depuración. Esto acelera su todo MUCHO !!! Por lo tanto, escribir código que depende de las llamadas a element.scope () es una mala idea. No hagas esto. La respuesta sobre la creación de una directiva personalizada es la forma correcta de hacerlo. @JQueryGuru, debes actualizar tu respuesta. Debido a que tiene la mayoría de los votos, la gente seguirá estos malos consejos.
helado
77
Esta solución interrumpirá la aplicación cuando la información de depuración esté deshabilitada. Desactivar eso para la producción es una recomendación oficial docs.angularjs.org/guide/production . Consulte también blog.thoughtram.io/angularjs/2014/12/22/…
wiherek
44
MALA SOLUCIÓN ... ... lea otros comentarios sobre 'desactivar la depuración' ... ... ... y vea sqrenla solución ... ... ... @ 0MV1
dsdsdsdsd
477

Hice una pequeña directiva para escuchar los cambios de entrada de archivos.

Ver JSFiddle

view.html:

<input type="file" custom-on-change="uploadFile">

controller.js:

app.controller('myCtrl', function($scope){
    $scope.uploadFile = function(event){
        var files = event.target.files;
    };
});     

directive.js:

app.directive('customOnChange', function() {
  return {
    restrict: 'A',
    link: function (scope, element, attrs) {
      var onChangeHandler = scope.$eval(attrs.customOnChange);
      element.on('change', onChangeHandler);
      element.on('$destroy', function() {
        element.off();
      });

    }
  };
});
sqren
fuente
44
Esto funciona bien si se elige un archivo diferente cada vez. ¿Es posible escuchar cuando también se selecciona el mismo archivo?
JDawg
12
Este tiene un "error" muy desafortunado: el controlador no está vinculado como "esto" en el CustomOnChange, ¡sino en la entrada del archivo! Me tiró por mucho tiempo y no pude encontrar el error real. Todavía no estoy seguro de cómo llamarlo realmente con "this" correctamente configurado.
Karel Bílek
44
el violín no funciona para mí, ya sea en Chrome o Firefox, a partir de hoy.
seguso
2
Esta respuesta es errónea según lo indicado por @ KarelBílek. No pude evitarlo y decidí usar angular-file-upload.
Gunar Gessner
2
Esto es absolutamente cómo hacerlo, funcionó a las mil maravillas. Además, ¿por qué usarías una función de depuración en producción?
Justin Kruse
42

Este es un refinamiento de algunos de los otros, los datos terminarán en un modelo ng, que normalmente es lo que desea.

Marcado (solo cree un archivo de datos de atributos para que la directiva pueda encontrarlo)

<input
    data-file
    id="id_image" name="image"
    ng-model="my_image_model" type="file">

JS

app.directive('file', function() {
    return {
        require:"ngModel",
        restrict: 'A',
        link: function($scope, el, attrs, ngModel){
            el.bind('change', function(event){
                var files = event.target.files;
                var file = files[0];

                ngModel.$setViewValue(file);
                $scope.$apply();
            });
        }
    };
});
Stuart Axon
fuente
1
Se $scope.$apply();requiere? Mi ng-changeevento todavía parece dispararse sin él.
fila1
1
@ row1 solo necesita disparar manualmente una aplicación si tiene otras cosas angulares que deben saber sobre ella durante esa operación.
Scott Sword
27

La manera limpia es escribir su propia directiva para enlazar al evento "change". Solo para hacerle saber que IE9 no es compatible con FormData, por lo que realmente no puede obtener el objeto de archivo del evento de cambio.

Puede usar la biblioteca ng-file-upload que ya es compatible con IE con FileAPI polyfill y simplificar la publicación del archivo en el servidor. Utiliza una directiva para lograr esto.

<script src="angular.min.js"></script>
<script src="ng-file-upload.js"></script>

<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) {
    //$files: an array of files selected, each file has name, size, and type.
    for (var i = 0; i < $files.length; i++) {
      var $file = $files[i];
      Upload.upload({
        url: 'my/upload/url',
        data: {file: $file}
      }).then(function(data, status, headers, config) {
        // file is uploaded successfully
        console.log(data);
      }); 
    }
  }
}];
danial
fuente
Este ejemplo está desactualizado. Obtenga uno nuevo de los documentos aquí .
Gunar Gessner
26

He ampliado la idea de @Stuart Axon de agregar un enlace bidireccional para la entrada del archivo (es decir, permitir restablecer la entrada restableciendo el valor del modelo a nulo):

app.directive('bindFile', [function () {
    return {
        require: "ngModel",
        restrict: 'A',
        link: function ($scope, el, attrs, ngModel) {
            el.bind('change', function (event) {
                ngModel.$setViewValue(event.target.files[0]);
                $scope.$apply();
            });

            $scope.$watch(function () {
                return ngModel.$viewValue;
            }, function (value) {
                if (!value) {
                    el.val("");
                }
            });
        }
    };
}]);

Manifestación

JLRishe
fuente
Gracias @JLRishe. Una nota, en realidad data-ng-click="vm.theFile=''"funciona bien, no es necesario escribirlo en un método de controlador
Sergey
20

Similar a algunas de las otras buenas respuestas aquí, escribí una directiva para resolver este problema, pero esta implementación refleja más de cerca la forma angular de adjuntar eventos.

Puede usar la directiva de esta manera:

HTML

<input type="file" file-change="yourHandler($event, files)" />

Como puede ver, puede inyectar los archivos seleccionados en su controlador de eventos, como inyectaría un objeto $ event en cualquier controlador de eventos ng.

Javascript

angular
  .module('yourModule')
  .directive('fileChange', ['$parse', function($parse) {

    return {
      require: 'ngModel',
      restrict: 'A',
      link: function ($scope, element, attrs, ngModel) {

        // Get the function provided in the file-change attribute.
        // Note the attribute has become an angular expression,
        // which is what we are parsing. The provided handler is 
        // wrapped up in an outer function (attrHandler) - we'll 
        // call the provided event handler inside the handler()
        // function below.
        var attrHandler = $parse(attrs['fileChange']);

        // This is a wrapper handler which will be attached to the
        // HTML change event.
        var handler = function (e) {

          $scope.$apply(function () {

            // Execute the provided handler in the directive's scope.
            // The files variable will be available for consumption
            // by the event handler.
            attrHandler($scope, { $event: e, files: e.target.files });
          });
        };

        // Attach the handler to the HTML change event 
        element[0].addEventListener('change', handler, false);
      }
    };
  }]);
Simon Robb
fuente
Luché por un par de días con esto hasta que probé tu respuesta. ¡Gracias!
Michael Tranchida
¿Cómo podría pasar un parámetro adicional a la fileChangeexpresión? Estoy usando esta directiva dentro de an ng-repeaty la $scopevariable pasada a attrHandleres la misma para cada registro. ¿Cuál es la mejor solución?
16

Esta directiva también pasa los archivos seleccionados:

/**
 *File Input - custom call when the file has changed
 */
.directive('onFileChange', function() {
  return {
    restrict: 'A',
    link: function (scope, element, attrs) {
      var onChangeHandler = scope.$eval(attrs.onFileChange);

      element.bind('change', function() {
        scope.$apply(function() {
          var files = element[0].files;
          if (files) {
            onChangeHandler(files);
          }
        });
      });

    }
  };
});

El HTML, cómo usarlo:

<input type="file" ng-model="file" on-file-change="onFilesSelected">

En mi controlador:

$scope.onFilesSelected = function(files) {
     console.log("files - " + files);
};
ingaham
fuente
¿Cómo se ve el $ scope.onFilesSelected en su controlador?
ingaham el
Si se abre un archivo y cuando intentamos cargarlo en el navegador Microsoft Edge. Nada esta pasando. ¿Puede usted ayudar?
Naveen Katakam
Sí, funciona bien con otros navegadores. Estoy enfrentando un problema en Microsoft Edge @ingaham
Naveen Katakam
8

Recomiendo crear una directiva

<input type="file" custom-on-change handler="functionToBeCalled(params)">

app.directive('customOnChange', [function() {
        'use strict';

        return {
            restrict: "A",

            scope: {
                handler: '&'
            },
            link: function(scope, element){

                element.change(function(event){
                    scope.$apply(function(){
                        var params = {event: event, el: element};
                        scope.handler({params: params});
                    });
                });
            }

        };
    }]);

Esta directiva se puede usar muchas veces, usa su propio alcance y no depende del alcance principal. También puede dar algunos parámetros a la función del controlador. La función del controlador se llamará con el objeto de alcance, que estaba activo cuando cambió la entrada. $ apply actualiza su modelo cada vez que se llama al evento de cambio

Julia Usanova
fuente
4

La versión angular más simple de jqLite.

JS:

.directive('cOnChange', function() {
    'use strict';

    return {
        restrict: "A",
        scope : {
            cOnChange: '&'
        },
        link: function (scope, element) {
            element.on('change', function () {
                scope.cOnChange();
        });
        }
    };
});

HTML:

<input type="file" data-c-on-change="your.functionName()">
Jonáš Krutil
fuente
Si se abre un archivo y cuando intentamos cargarlo en el navegador Microsoft Edge. Nada esta pasando. ¿Puede usted ayudar?
Naveen Katakam
2

Demostración de trabajo de la directiva de "entrada de archivos" que funciona con ng-change1

Para hacer que un <input type=file>elemento funcione con la directiva ng-change , necesita una directiva personalizada que funcione con la directiva ng-model .

<input type="file" files-input ng-model="fileList" 
       ng-change="onInputChange()" multiple />

La DEMO

angular.module("app",[])
.directive("filesInput", function() {
  return {
    require: "ngModel",
    link: function postLink(scope,elem,attrs,ngModel) {
      elem.on("change", function(e) {
        var files = elem[0].files;
        ngModel.$setViewValue(files);
      })
    }
  }
})

.controller("ctrl", function($scope) {
     $scope.onInputChange = function() {
         console.log("input change");
     };
})
<script src="//unpkg.com/angular/angular.js"></script>
  <body ng-app="app" ng-controller="ctrl">
    <h1>AngularJS Input `type=file` Demo</h1>
    
    <input type="file" files-input ng-model="fileList" 
           ng-change="onInputChange()" multiple />
    
    <h2>Files</h2>
    <div ng-repeat="file in fileList">
      {{file.name}}
    </div>
  </body>

georgeawg
fuente
¡Genial pero la directiva solo se llama una vez! Si cambio el archivo seleccionado, ¡la directiva no se llama por segunda vez! ¿Tienes alguna idea sobre este comportamiento?
Hugsbrugs
La DEMO no tiene ese problema. Cree una nueva pregunta con un ejemplo reproducible para que los lectores la examinen.
georgeawg
sí, tiene ese problema, el "cambio de entrada" del registro de la consola solo se muestra una vez, incluso si cambia los archivos seleccionados muchas veces.
abrazosbrugs
después de la prueba, funciona en cromo pero no en la última versión de Firefox ... ¡¡¡maldita compatibilidad del navegador !!!
abrazosbrugs
0

Solución demasiado completa basada en:

`onchange="angular.element(this).scope().UpLoadFile(this.files)"`

Una manera simple de ocultar el campo de entrada y reemplazarlo con una imagen, aquí después de una solución, que también requiere un corte angular pero que hace el trabajo [TriggerEvent no funciona como se esperaba]

La solución:

  • coloque el campo de entrada en la pantalla: ninguno [el campo de entrada existe en el DOM pero no está visible]
  • coloque su imagen justo después En la imagen, use nb-click () para activar un método

Cuando se hace clic en la imagen, simule una acción DOM 'clic' en el campo de entrada. Et voilà!

 var tmpl = '<input type="file" id="{{name}}-filein"' + 
             'onchange="angular.element(this).scope().UpLoadFile(this.files)"' +
             ' multiple accept="{{mime}}/*" style="display:none" placeholder="{{placeholder}}">'+
             ' <img id="{{name}}-img" src="{{icon}}" ng-click="clicked()">' +
             '';
   // Image was clicked let's simulate an input (file) click
   scope.inputElem = elem.find('input'); // find input in directive
   scope.clicked = function () {
         console.log ('Image clicked');
         scope.inputElem[0].click(); // Warning Angular TriggerEvent does not work!!!
    };
Fulup
fuente
1
No puede usar angular.element (this) .scope (), ¡esta es una función de depuración!
Cesar
0

Lo he hecho así;

<!-- HTML -->
<button id="uploadFileButton" class="btn btn-info" ng-click="vm.upload()">    
<span  class="fa fa-paperclip"></span></button>
<input type="file" id="txtUploadFile" name="fileInput" style="display: none;" />
// self is the instance of $scope or this
self.upload = function () {
   var ctrl = angular.element("#txtUploadFile");
   ctrl.on('change', fileNameChanged);
   ctrl.click();
}

function fileNameChanged(e) {
    console.log(self.currentItem);
    alert("select file");
}
Ali Sakhi
fuente
0

Otra forma interesante de escuchar los cambios de entrada del archivo es vigilando el atributo ng-model del archivo de entrada. Por supuesto, FileModel es una directiva personalizada.

Me gusta esto:

HTML -> <input type="file" file-model="change.fnEvidence">

Código JS ->

$scope.$watch('change.fnEvidence', function() {
                    alert("has changed");
                });

Espero que pueda ayudar a alguien.

halbano
fuente
0

Los elementos angulares (como el elemento raíz de una directiva) son objetos jQuery [Lite]. Esto significa que podemos registrar el escucha de eventos de esta manera:

link($scope, $el) {
    const fileInputSelector = '.my-file-input'

    function setFile() {
        // access file via $el.find(fileInputSelector).get(0).files[0]
    }

    $el.on('change', fileInputSelector, setFile)
}

Esta es la delegación de eventos jQuery. Aquí, el oyente está conectado al elemento raíz de la directiva. Cuando se activa el evento, se elevará al elemento registrado y jQuery determinará si el evento se originó en un elemento interno que coincida con el selector definido. Si lo hace, el controlador disparará.

Los beneficios de este método son:

  • el controlador está vinculado al elemento $ que se limpiará automáticamente cuando se destruya el alcance de la directiva.
  • sin código en la plantilla
  • funcionará incluso si el delegado de destino (entrada) aún no se ha procesado cuando registra el controlador de eventos (como cuando se usa ng-ifo ng-switch)

http://api.jquery.com/on/

Matt S
fuente
-1

Simplemente puede agregar el siguiente código en onchange y detectará el cambio. puede escribir una función con X clic o algo para eliminar los datos del archivo.

document.getElementById(id).value = "";
Jijo Thomas
fuente
Esta es una mala práctica y no angular.
Sebastian