AngularJs: como verificar alterações nos campos de entrada de arquivo?

281

Eu sou novo em angular. Estou tentando ler o caminho do arquivo carregado no campo 'arquivo' do HTML sempre que uma 'alteração' acontece nesse campo. Se eu usar o 'onChange', ele funcionará, mas quando o usar de maneira angular, usando o 'ng-change', não funcionará.

<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 está chamando. O Firebug também não mostra nenhum erro.

Manish Kumar
fonte
1
Poderia ser útil: stackoverflow.com/q/17063000/983992
Marcel Gwerder

Respostas:

240

Nenhum suporte de ligação para o controle de Upload de Arquivos

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>

ao invés de

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

você pode tentar

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

Nota: isso requer que o aplicativo angular esteja sempre no modo de depuração . Isso não funcionará no código de produção se o modo de depuração estiver desativado.

e na sua função muda em vez de

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

você pode tentar

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

Abaixo está um exemplo prático de upload de arquivo com arrastar e soltar upload de arquivo pode ser útil http://jsfiddle.net/danielzen/utp7j/

Informações de upload de arquivo angular

URL para upload de arquivo AngularJS no ASP.Net

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

Upload de vários arquivos nativos do AngularJs com progresso com o NodeJS

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

ngUpload - um serviço AngularJS para fazer upload de arquivos usando iframe

http://ngmodules.org/modules/ngUpload

JQuery Guru
fonte
2
onchange = "angular.element (this) .scope (). fileNameChaged (this)" $ scope.fileNameChaged = function (elemento) {console.log ('arquivos:', element.files); } Você pode mudar em dois lugares e verificar? dependendo do navegador que você vai ter o caminho completo do arquivo que eu tenho atualizar o violino abaixo é o arquivo url upload e consola verificação jsfiddle.net/utp7j/260
JQuery Guru
13
Esse método contorna o processamento padrão do AngularJS; portanto, $ scope. $ Digest () não é chamado (as ligações não são atualizadas). Chame 'angular.element (this) .scope (). $ Digest ()' posteriormente ou chame um método de escopo que use $ apply.
Ramon de Klein
113
Esta é uma resposta horrível. Chamar angular.element (blah) .scope () é um recurso de depuração que você deve usar apenas para depuração. Angular não usa o recurso .scope. Está lá apenas para ajudar os desenvolvedores a depurar. Ao criar seu aplicativo e implantar na produção, você deve desativar as informações de depuração. Isso acelera o seu MUITO !!! Portanto, escrever código que depende das chamadas element.scope () é uma má idéia. Não faça isso. A resposta sobre a criação de uma diretiva personalizada é a maneira correta de fazer isso. @JQueryGuru, você deve atualizar sua resposta. Por ter o maior número de votos, as pessoas seguirão esse mau conselho.
gelado
7
Esta solução interromperá o aplicativo quando as informações de depuração estiverem desabilitadas. Desabilitar isso para produção é uma recomendação oficial docs.angularjs.org/guide/production . Consulte também blog.thoughtram.io/angularjs/2014/12/22/…
wiherek 13/02/2015
4
MAU SOLUÇÃO ... ... leia outros comentários sobre 'desativando a depuração' ... ... ... e veja sqrena solução ... ... ... @ 0MV1
dsdsdsdsd 14/16
477

Fiz uma pequena diretiva para ouvir as alterações na entrada do arquivo.

Exibir 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
fonte
4
Isso funciona bem se um arquivo diferente for escolhido a cada vez. É possível escutar quando o mesmo arquivo também é selecionado?
JDawg # 11/15
12
Isso tem um "bug" muito infeliz - o controlador não está vinculado como "this" no customOnChange, mas na entrada do arquivo! Isso me assustou por um longo tempo e eu não consegui encontrar o bug real. Ainda não sei como chamá-lo com o conjunto "this" corretamente.
Karel Bílek
4
o violino não funciona para mim, nem no Chrome nem no Firefox, a partir de hoje.
seguso
2
Esta resposta é falha, como afirma @ KarelBílek. Não consegui contornar o problema e decidi usar o upload de arquivo angular.
Gunar Gessner
2
Isto é absolutamente como fazê-lo, funcionou como um encanto. Além disso, por que você usaria um recurso de depuração na produção?
Justin Kruse
42

Este é um refinamento de alguns dos outros, os dados acabam em um modelo ng, que normalmente é o que você deseja.

Marcação (basta criar um arquivo de dados de atributo para que a diretiva possa encontrá-lo)

<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
fonte
1
É $scope.$apply();obrigatório? Meu ng-changeevento ainda parece disparar sem ele.
191
1
@ row1, você só precisa disparar um aplicativo manualmente se tiver outras coisas angulares que precisam saber sobre ele durante essa operação.
9789 Scott Scord
27

A maneira limpa é escrever sua própria diretiva para vincular ao evento "change". Apenas para informar que o IE9 não suporta FormData, você não pode realmente obter o objeto de arquivo do evento de alteração.

Você pode usar a biblioteca ng-upload de arquivos que já suporta o IE com o polyfill FileAPI e simplificar a postagem do arquivo no servidor. Ele usa uma diretiva para conseguir isso.

<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
fonte
Este exemplo está desatualizado. Obtenha um novo dos documentos aqui .
Gunar Gessner
26

Eu expandi a ideia de @Stuart Axon para adicionar ligação bidirecional para a entrada do arquivo (ou seja, permitir redefinir a entrada redefinindo o valor do modelo para 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("");
                }
            });
        }
    };
}]);

Demo

JLRishe
fonte
Obrigado @JLRishe. Uma nota, na verdade, data-ng-click="vm.theFile=''"funciona bem, não há necessidade de escrevê-lo a um método de controlador
Sergey
20

Semelhante a algumas das outras boas respostas aqui, escrevi uma diretiva para resolver esse problema, mas essa implementação reflete mais de perto a maneira angular de anexar eventos.

Você pode usar a diretiva assim:

HTML

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

Como você pode ver, você pode injetar os arquivos selecionados em seu manipulador de eventos, como injetaria um objeto $ event em qualquer manipulador 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
fonte
lutou por alguns dias com isso até que eu tentei sua resposta. Obrigado!
Michael Tranchida
Como eu poderia passar um parâmetro adicional para a fileChangeexpressão? Estou usando esta diretiva dentro de um ng-repeate a $scopevariável passada para attrHandleré a mesma para cada registro. Qual a melhor solução?
16

Esta diretiva passa os arquivos selecionados também:

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

    }
  };
});

O HTML, como usá-lo:

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

No meu controlador:

$scope.onFilesSelected = function(files) {
     console.log("files - " + files);
};
Ingaham
fonte
Como o $ scope.onFilesSelected se parece no seu controlador?
ingaham
Se um arquivo for aberto e quando tentarmos fazer o upload no arquivo no navegador Microsoft Edge. Nada está acontecendo. Você pode ajudar?
Naveen Katakam
Sim, está funcionando bem com outros navegadores. eu estou enfrentando problema no @ingaham borda microsoft
Naveen Katakam
8

Eu recomendo criar uma diretiva

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

        };
    }]);

Essa diretiva pode ser usada várias vezes, usa seu próprio escopo e não depende do escopo pai. Você também pode fornecer alguns parâmetros para a função de manipulador. A função do manipulador será chamada com o objeto de escopo, que estava ativo quando você alterou a entrada. $ apply atualiza seu modelo sempre que o evento de mudança é chamado

Julia Usanova
fonte
4

A versão mais simples do jqLite Angular.

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
fonte
Se um arquivo for aberto e quando tentarmos fazer o upload no arquivo no navegador Microsoft Edge. Nada está acontecendo. Você pode ajudar?
Naveen Katakam
2

Demonstração de trabalho da diretiva "entrada de arquivos" que funciona com ng-change1

Para que um <input type=file>elemento funcione na diretiva ng-change , ele precisa de uma diretiva personalizada que funcione com a diretiva ng-model .

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

A 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
fonte
Ótimo, mas a diretiva é chamada apenas uma vez! Se eu alterar o arquivo selecionado, a diretiva não será chamada pela segunda vez! Você tem alguma idéia sobre esse comportamento?
hugsbrugs
A DEMO não tem esse problema. Crie uma nova pergunta com um exemplo reproduzível para os leitores examinarem.
georgeawg 5/04
Sim, há esse problema. O log do console "entrada alterada" é exibido apenas uma vez, mesmo que você altere os arquivos selecionados várias vezes!
hugsbrugs
após o teste, ele funciona com cromo, mas não na versão mais recente do firefox ... maldita compatibilidade com o navegador !!!
hugsbrugs 6/04
0

Base de solução muito completa em:

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

Uma maneira simples de ocultar o campo de entrada e substituí-lo por uma imagem, aqui após uma solução, que também exige um hack no angular, mas que faz o trabalho [TriggerEvent não funciona conforme o esperado]

A solução:

  • coloque o campo de entrada em exibição: nenhum [o campo de entrada existe no DOM, mas não é visível]
  • coloque sua imagem logo após Na imagem, use nb-click () para ativar um método

Quando a imagem é clicada, simule uma ação do DOM 'clique' no 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
fonte
1
Você não pode usar angular.element (this) .scope (), este é um recurso de depuração!
Cesar
0

Eu fiz assim;

<!-- 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
fonte
0

Outra maneira interessante de ouvir as alterações na entrada do arquivo é observando o atributo ng-model do arquivo de entrada. Claro que o FileModel é uma diretiva personalizada.

Como isso:

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

Código JS ->

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

Espero que possa ajudar alguém.

halbano
fonte
0

Elementos angulares (como o elemento raiz de uma diretiva) são objetos jQuery [Lite]. Isso significa que podemos registrar o ouvinte de eventos da seguinte maneira:

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 é a delegação de eventos jQuery. Aqui, o ouvinte é anexado ao elemento raiz da diretiva. Quando o evento é acionado, ele borbulha para o elemento registrado e o jQuery determina se o evento se originou em um elemento interno que corresponde ao seletor definido. Se isso acontecer, o manipulador será acionado.

Os benefícios deste método são:

  • o manipulador é vinculado ao elemento $, que será limpo automaticamente quando o escopo da diretiva for destruído.
  • nenhum código no modelo
  • funcionará mesmo se o delegado de destino (entrada) ainda não tiver sido renderizado quando você registrar o manipulador de eventos (como ao usar ng-ifou ng-switch)

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

Matt S
fonte
-1

Você pode simplesmente adicionar o código abaixo no onchange e ele detectará alterações. você pode escrever uma função no X click ou algo para remover os dados do arquivo.

document.getElementById(id).value = "";
Jijo Thomas
fonte
Esta é uma prática ruim e não angular.
Sebastian