AngularJs: Comment vérifier les changements dans les champs de saisie de fichier?

281

Je suis nouveau à angulaire. J'essaie de lire le chemin du fichier téléchargé à partir du champ "fichier" HTML chaque fois qu'un "changement" se produit sur ce champ. Si j'utilise 'onChange' cela fonctionne mais quand je l'utilise de manière angulaire en utilisant 'ng-change' cela ne fonctionne pas.

<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 () n'appelle jamais. Firebug ne montre également aucune erreur.

Manish Kumar
la source
1
Pourrait être utile: stackoverflow.com/q/17063000/983992
Marcel Gwerder

Réponses:

240

Aucune prise en charge de liaison pour le contrôle de téléchargement de fichiers

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>

au lieu de

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

peux-tu essayer

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

Remarque: cela nécessite que l'application angulaire soit toujours en mode débogage . Cela ne fonctionnera pas dans le code de production si le mode de débogage est désactivé.

et dans votre fonction change au lieu de

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

peux-tu essayer

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

Vous trouverez ci-dessous un exemple de téléchargement de fichiers avec téléchargement de fichiers par glisser-déposer. Http://jsfiddle.net/danielzen/utp7j/

Informations sur le téléchargement de fichiers angulaires

URL pour le téléchargement de fichiers AngularJS dans ASP.Net

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

Téléchargement multi-fichiers natif AngularJs avec progression avec NodeJS

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

ngUpload - Un service AngularJS pour télécharger des fichiers en utilisant iframe

http://ngmodules.org/modules/ngUpload

JQuery Guru
la source
2
onchange = "angular.element (this) .scope (). fileNameChaged (this)" $ scope.fileNameChaged = function (element) {console.log ('files:', element.files); } Pouvez-vous changer à deux endroits et vérifier? selon le navigateur que vous obtiendrez le fichier chemin complet j'ai mettre à jour le violon ci - dessous est le fichier de téléchargement url et la console de contrôle jsfiddle.net/utp7j/260
JQuery Guru
13
Cette méthode contourne le traitement AngularJS par défaut, donc $ scope. $ Digest () n'est pas appelé (les liaisons ne sont pas mises à jour). Appelez ensuite 'angular.element (this) .scope (). $ Digest ()' ou appelez une méthode de portée qui utilise $ apply.
Ramon de Klein
113
Ceci est une réponse horrible. L'appel à angular.element (blah) .scope () est une fonctionnalité de débogage que vous ne devez utiliser que pour le débogage. Angular n'utilise pas la fonction .scope. Il n'est là que pour aider les développeurs à déboguer. Lorsque vous créez votre application et déployez en production, vous êtes censé désactiver les informations de débogage. Cela accélère beaucoup! Donc, écrire du code qui dépend des appels à element.scope () est une mauvaise idée. Ne fais pas ça. La réponse à la création d'une directive personnalisée est la bonne façon de procéder. @JQueryGuru, vous devez mettre à jour votre réponse. Parce qu'il a le plus de votes, les gens suivront ce mauvais conseil.
givré
7
Cette solution interrompra l'application lorsque les informations de débogage sont désactivées. Désactiver cela pour la production est une recommandation officielle docs.angularjs.org/guide/production . S'il vous plaît voir aussi blog.ilsttram.io/angularjs/2014/12/22/…
wiherek
4
MAUVAISE SOLUTION ... ... lire d'autres commentaires sur 'désactiver le débogage' ... ... ... et voir sqrenla solution ... ... ... @ 0MV1
dsdsdsdsd
477

J'ai fait une petite directive pour écouter les changements d'entrée de fichier.

Voir 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
la source
4
Cela fonctionne bien si un fichier différent est choisi à chaque fois. Est-il possible d'écouter quand le même fichier est également sélectionné?
JDawg
12
Cela a un "bug" très malheureux - le contrôleur n'est pas lié comme "ceci" dans customOnChange, mais l'entrée de fichier! Cela m'a jeté longtemps et je n'ai pas pu trouver le vrai bug. Je ne sais pas encore comment l'appeler réellement avec "this" correctement réglé.
Karel Bílek
4
le violon ne fonctionne pas pour moi, que ce soit sur Chrome ou Firefox, à partir d'aujourd'hui.
seguso
2
Cette réponse est imparfaite comme indiqué par @ KarelBílek. Je n'ai pas pu le contourner et j'ai décidé d'utiliser le téléchargement de fichiers angulaires.
Gunar Gessner
2
C'est absolument comme ça que ça marche, ça marche comme un charme. D'ailleurs, pourquoi utiliseriez-vous une fonction de débogage en production?
Justin Kruse
42

Ceci est un raffinement de certains des autres, les données se retrouveront dans un modèle ng, qui est normalement ce que vous voulez.

Balisage (créez simplement un fichier de données d'attribut pour que la directive puisse le trouver)

<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
la source
1
Est $scope.$apply();requis? Mon ng-changeévénement semble toujours se déclencher sans lui.
row1
1
@ row1, vous devez uniquement déclencher manuellement une application si vous avez d'autres informations angulaires à connaître lors de cette opération.
Scott Sword
27

La meilleure façon est d'écrire votre propre directive à lier à l'événement "change". Juste pour vous faire savoir qu'IE9 ne prend pas en charge FormData, vous ne pouvez donc pas vraiment obtenir l'objet fichier à partir de l'événement change.

Vous pouvez utiliser la bibliothèque ng-file-upload qui prend déjà en charge IE avec FileAPI polyfill et simplifier la publication du fichier sur le serveur. Il utilise une directive pour y parvenir.

<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
la source
Cet exemple est obsolète. Obtenez un nouveau document ici .
Gunar Gessner
26

J'ai développé l'idée de @Stuart Axon d'ajouter une liaison bidirectionnelle pour l'entrée de fichier (c'est-à-dire permettre de réinitialiser l'entrée en réinitialisant la valeur du modèle à null):

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("");
                }
            });
        }
    };
}]);

Démo

JLRishe
la source
Merci @JLRishe. Une note, data-ng-click="vm.theFile=''"fonctionne vraiment bien, pas besoin de l'écrire dans une méthode de contrôleur
Sergey
20

Semblable à certaines des autres bonnes réponses ici, j'ai écrit une directive pour résoudre ce problème, mais cette implémentation reflète de plus près la façon angulaire d'attacher des événements.

Vous pouvez utiliser la directive comme ceci:

HTML

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

Comme vous pouvez le voir, vous pouvez injecter les fichiers sélectionnés dans votre gestionnaire d'événements, comme vous injecteriez un objet $ event dans n'importe quel gestionnaire d'événements 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
la source
lutté pendant quelques jours avec cela jusqu'à ce que j'essaye votre réponse. Merci!
Michael Tranchida
Comment pourrais-je passer un paramètre supplémentaire à l' fileChangeexpression? J'utilise cette directive à l'intérieur d'un ng-repeatet la $scopevariable transmise à attrHandlerest la même pour chaque enregistrement. Quelle est la meilleure solution?
16

Cette directive transmet également les fichiers sélectionnés:

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

    }
  };
});

Le HTML, comment l'utiliser:

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

Dans mon contrôleur:

$scope.onFilesSelected = function(files) {
     console.log("files - " + files);
};
ingaham
la source
À quoi ressemble $ scope.onFilesSelected dans votre contrôleur?
ingaham
Si un fichier est ouvert et lorsque nous essayons de le télécharger dans le navigateur Microsoft Edge. Il ne se passe rien. Pouvez-vous m'aider?
Naveen Katakam
Oui, cela fonctionne bien avec d'autres navigateurs. je suis confronté à un problème dans Microsoft Edge @ingaham
Naveen Katakam
8

Je recommande de créer une directive

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

        };
    }]);

cette directive peut être utilisée plusieurs fois, elle utilise sa propre portée et ne dépend pas de la portée parent. Vous pouvez également donner quelques paramètres à la fonction de gestionnaire. La fonction de gestionnaire sera appelée avec l'objet scope, qui était actif lorsque vous avez modifié l'entrée. $ apply met à jour votre modèle à chaque appel de l'événement change

Julia Usanova
la source
4

La version jqLite angulaire la plus simple.

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
la source
Si un fichier est ouvert et lorsque nous essayons de le télécharger dans le navigateur Microsoft Edge. Il ne se passe rien. Pouvez-vous m'aider?
Naveen Katakam
2

Démonstration de travail de la directive «entrée de fichiers» qui fonctionne avec ng-change1

Pour faire fonctionner un <input type=file>élément avec la directive ng-change , il a besoin d'une directive personnalisée qui fonctionne avec la directive 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
la source
Super mais la directive n'est appelée qu'une seule fois! Si je change le fichier sélectionné, la directive n'est pas appelée une deuxième fois! Avez-vous une idée de ce comportement?
hugsbrugs
La DEMO n'a pas ce problème. Créez une nouvelle question avec un exemple reproductible que les lecteurs pourront examiner.
georgeawg
oui, il y a ce problème, le journal de la console "changement d'entrée" ne s'affiche qu'une seule fois même si vous modifiez plusieurs fois les fichiers sélectionnés!
hugsbrugs
après les tests, il fonctionne sur le chrome mais pas sur la dernière version de Firefox ... putain de compatibilité de navigateur !!!
hugsbrugs
0

Base de solution trop complète sur:

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

Un moyen simple de masquer le champ de saisie et de le remplacer par une image, ici après une solution, qui nécessite également un hack sur angulaire mais qui fait le travail [TriggerEvent ne fonctionne pas comme prévu]

La solution:

  • place le champ de saisie dans display: aucun [le champ de saisie existe dans le DOM mais n'est pas visible]
  • placez votre image juste après Sur l'image utilisez nb-click () pour activer une méthode

Lorsque vous cliquez sur l'image, simulez une action DOM «cliquez» sur le champ de saisie. 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
la source
1
Vous ne pouvez pas utiliser angular.element (this) .scope (), c'est une fonction de débogage!
Cesar
0

Je l'ai fait comme ça;

<!-- 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
la source
0

Une autre façon intéressante d'écouter les modifications d'entrée de fichier est de surveiller l'attribut ng-model du fichier d'entrée. Bien sûr, FileModel est une directive personnalisée.

Comme ça:

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

Code JS ->

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

J'espère que cela peut aider quelqu'un.

halbano
la source
0

Les éléments angulaires (tels que l'élément racine d'une directive) sont des objets jQuery [Lite]. Cela signifie que nous pouvons enregistrer l'auditeur d'événements comme ceci:

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

Il s'agit de la délégation d'événement jQuery. Ici, l'écouteur est attaché à l'élément racine de la directive. Lorsque l'événement est déclenché, il bouillonne jusqu'à l'élément enregistré et jQuery détermine si l'événement est originaire d'un élément interne correspondant au sélecteur défini. Si c'est le cas, le gestionnaire se déclenche.

Les avantages de cette méthode sont:

  • le gestionnaire est lié à l'élément $ qui sera automatiquement nettoyé lorsque la portée de la directive est détruite.
  • pas de code dans le modèle
  • fonctionnera même si le délégué cible (entrée) n'a pas encore été rendu lorsque vous enregistrez le gestionnaire d'événements (comme lors de l'utilisation de ng-ifou ng-switch)

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

Matt S
la source
-1

Vous pouvez simplement ajouter le code ci-dessous dans onchange et il détectera le changement. vous pouvez écrire une fonction sur un clic X ou quelque chose pour supprimer les données du fichier ..

document.getElementById(id).value = "";
Jijo Thomas
la source
C'est une mauvaise pratique et non angulaire.
Sebastian