web-dev-qa-db-fra.com

ng-model pour `<input type =" file "/>` (avec directive DEMO)

J'ai essayé d'utiliser ng-model sur une balise input avec un fichier type:

<input type="file" ng-model="vm.uploadme" />

Mais après avoir sélectionné un fichier, $ scope.vm.uploadme est toujours non défini dans le contrôleur.

Comment puis-je obtenir le fichier sélectionné dans mon contrôleur?

266
Endy Tjahjono

J'ai créé une solution de contournement avec directive:

.directive("fileread", [function () {
    return {
        scope: {
            fileread: "="
        },
        link: function (scope, element, attributes) {
            element.bind("change", function (changeEvent) {
                var reader = new FileReader();
                reader.onload = function (loadEvent) {
                    scope.$apply(function () {
                        scope.fileread = loadEvent.target.result;
                    });
                }
                reader.readAsDataURL(changeEvent.target.files[0]);
            });
        }
    }
}]);

Et la balise d'entrée devient:

<input type="file" fileread="vm.uploadme" />

Ou si seulement la définition du fichier est nécessaire:

.directive("fileread", [function () {
    return {
        scope: {
            fileread: "="
        },
        link: function (scope, element, attributes) {
            element.bind("change", function (changeEvent) {
                scope.$apply(function () {
                    scope.fileread = changeEvent.target.files[0];
                    // or all selected files:
                    // scope.fileread = changeEvent.target.files;
                });
            });
        }
    }
}]);
317
Endy Tjahjono

J'utilise cette directive:

angular.module('appFilereader', []).directive('appFilereader', function($q) {
    var slice = Array.prototype.slice;

    return {
        restrict: 'A',
        require: '?ngModel',
        link: function(scope, element, attrs, ngModel) {
                if (!ngModel) return;

                ngModel.$render = function() {};

                element.bind('change', function(e) {
                    var element = e.target;

                    $q.all(slice.call(element.files, 0).map(readFile))
                        .then(function(values) {
                            if (element.multiple) ngModel.$setViewValue(values);
                            else ngModel.$setViewValue(values.length ? values[0] : null);
                        });

                    function readFile(file) {
                        var deferred = $q.defer();

                        var reader = new FileReader();
                        reader.onload = function(e) {
                            deferred.resolve(e.target.result);
                        };
                        reader.onerror = function(e) {
                            deferred.reject(e);
                        };
                        reader.readAsDataURL(file);

                        return deferred.promise;
                    }

                }); //change

            } //link
    }; //return
});

et l'invoque comme ceci:

<input type="file" ng-model="editItem._attachments_uri.image" accept="image/*" app-filereader />

La propriété (editItem.editItem._attachments_uri.image) sera renseignée avec le contenu du fichier sélectionné en tant que data-uri (!).

S'il vous plaît noter que ce script ne téléchargera rien. Cela remplira votre modèle uniquement avec le contenu de votre fichier encodé et un data-uri (base64).

Découvrez une démonstration de travail ici: http://plnkr.co/CMiHKv2BEidM9SShm9Vv

53
Elmer

Ceci est un addendum à la solution de @ endy-tjahjono. 

J'ai fini par ne pas pouvoir obtenir la valeur de uploadme de la portée. Même si uploadme dans le code HTML était visiblement mis à jour par la directive, je ne pouvais toujours pas accéder à sa valeur par $ scope.uploadme. J'ai pu définir sa valeur à partir de la portée, cependant. Mystérieux, non ..?

Il s'est avéré que la directive a créé une étendue enfant et qu'elle avait son propre uploadme

La solution consistait à utiliser un objet plutôt qu'une primitive pour conserver la valeur de uploadme .

Dans le contrôleur j'ai: 

$scope.uploadme = {};
$scope.uploadme.src = "";

et dans le HTML:

 <input type="file" fileread="uploadme.src"/>
 <input type="text" ng-model="uploadme.src"/>

Il n'y a aucun changement à la directive. 

Maintenant, tout fonctionne comme prévu. Je peux récupérer la valeur de uploadme.src de mon contrôleur à l’aide de $ scope.uploadme.

27

Démonstration de la directive qui fonctionne avec ng-model

Comment activer <input type="file"> pour travailler avec ng-model

La directive de base ng-model ne fonctionne pas avec <input type="file" prêt à l'emploi.

Cette directive personnalisée active ng-model et présente l'avantage supplémentaire d'activer les directives ng-change, ng-required et ng-form pour qu'elles fonctionnent avec <input type="file">

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>

    <code><table ng-show="fileArray.length">
    <tr><td>Name</td><td>Date</td><td>Size</td><td>Type</td><tr>
    <tr ng-repeat="file in fileArray">
      <td>{{file.name}}</td>
      <td>{{file.lastModified | date  : 'MMMdd,yyyy'}}</td>
      <td>{{file.size}}</td>
      <td>{{file.type}}</td>
    </tr>
    </table></code>
    
  </body>

22
georgeawg

Salut les gars, je crée une directive et inscrit sur Bower.

cette bibliothèque vous aidera à modéliser le fichier d’entrée, non seulement à renvoyer les données du fichier, mais également au fichier dataurl ou base 64.

{
    "lastModified": 1438583972000,
    "lastModifiedDate": "2015-08-03T06:39:32.000Z",
    "name": "gitignore_global.txt",
    "size": 236,
    "type": "text/plain",
    "data": "data:text/plain;base64,DQojaWdub3JlIHRodW1ibmFpbHMgY3JlYXRlZCBieSB3aW5kb3dz…xoDQoqLmJhaw0KKi5jYWNoZQ0KKi5pbGsNCioubG9nDQoqLmRsbA0KKi5saWINCiouc2JyDQo="
}

https://github.com/mistralworks/ng-file-model/

L'espoir vous aidera

9
yozawiratama

C'est une version légèrement modifiée qui vous permet de spécifier le nom de l'attribut dans l'étendue, comme vous le feriez avec ng-model, utilisation: 

    <myUpload key="file"></myUpload>

Directif:

.directive('myUpload', function() {
    return {
        link: function postLink(scope, element, attrs) {
            element.find("input").bind("change", function(changeEvent) {                        
                var reader = new FileReader();
                reader.onload = function(loadEvent) {
                    scope.$apply(function() {
                        scope[attrs.key] = loadEvent.target.result;                                
                    });
                }
                if (typeof(changeEvent.target.files[0]) === 'object') {
                    reader.readAsDataURL(changeEvent.target.files[0]);
                };
            });

        },
        controller: 'FileUploadCtrl',
        template:
                '<span class="btn btn-success fileinput-button">' +
                '<i class="glyphicon glyphicon-plus"></i>' +
                '<span>Replace Image</span>' +
                '<input type="file" accept="image/*" name="files[]" multiple="">' +
                '</span>',
        restrict: 'E'

    };
});
4
asiop

Pour la saisie de plusieurs fichiers à l'aide de lodash ou de soulignement:

.directive("fileread", [function () {
    return {
        scope: {
            fileread: "="
        },
        link: function (scope, element, attributes) {
            element.bind("change", function (changeEvent) {
                return _.map(changeEvent.target.files, function(file){
                  scope.fileread = [];
                  var reader = new FileReader();
                  reader.onload = function (loadEvent) {
                      scope.$apply(function () {
                          scope.fileread.Push(loadEvent.target.result);
                      });
                  }
                  reader.readAsDataURL(file);
                });
            });
        }
    }
}]);
3
Uelb

Je devais faire la même chose sur plusieurs entrées, aussi j’ai mis à jour la méthode @Endy Tjahjono .. .. Elle renvoie un tableau contenant tous les fichiers lus.

  .directive("fileread", function () {
    return {
      scope: {
        fileread: "="
      },
      link: function (scope, element, attributes) {
        element.bind("change", function (changeEvent) {
          var readers = [] ,
              files = changeEvent.target.files ,
              datas = [] ;
          for ( var i = 0 ; i < files.length ; i++ ) {
            readers[ i ] = new FileReader();
            readers[ i ].onload = function (loadEvent) {
              datas.Push( loadEvent.target.result );
              if ( datas.length === files.length ){
                scope.$apply(function () {
                  scope.fileread = datas;
                });
              }
            }
            readers[ i ].readAsDataURL( files[i] );
          }
        });

      }
    }
  });
2
Hugo

function filesModelDirective(){
  return {
    controller: function($parse, $element, $attrs, $scope){
      var exp = $parse($attrs.filesModel);
      $element.on('change', function(){
        exp.assign($scope, this.files[0]);
        $scope.$apply();
      });
    }
  };
}
app.directive('filesModel', filesModelDirective);

2
coder000001

J'ai dû modifier la directive Endy pour pouvoir obtenir les informations Last Modified, lastModifiedDate, nom, taille, type et données, ainsi que pour pouvoir obtenir un tableau de fichiers. Pour ceux d'entre vous qui avaient besoin de ces fonctionnalités supplémentaires, c'est parti.

UPDATE: J'ai trouvé un bogue dans lequel si vous sélectionnez le (s) fichier (s), puis que vous sélectionnez à nouveau mais annulez à la place, les fichiers ne sont jamais désélectionnés comme il apparaît. J'ai donc mis à jour mon code pour résoudre ce problème.

 .directive("fileread", function () {
        return {
            scope: {
                fileread: "="
            },
            link: function (scope, element, attributes) {
                element.bind("change", function (changeEvent) {
                    var readers = [] ,
                        files = changeEvent.target.files ,
                        datas = [] ;
                    if(!files.length){
                        scope.$apply(function () {
                            scope.fileread = [];
                        });
                        return;
                    }
                    for ( var i = 0 ; i < files.length ; i++ ) {
                        readers[ i ] = new FileReader();
                        readers[ i ].index = i;
                        readers[ i ].onload = function (loadEvent) {
                            var index = loadEvent.target.index;
                            datas.Push({
                                lastModified: files[index].lastModified,
                                lastModifiedDate: files[index].lastModifiedDate,
                                name: files[index].name,
                                size: files[index].size,
                                type: files[index].type,
                                data: loadEvent.target.result
                            });
                            if ( datas.length === files.length ){
                                scope.$apply(function () {
                                    scope.fileread = datas;
                                });
                            }
                        };
                        readers[ i ].readAsDataURL( files[i] );
                    }
                });

            }
        }
    });
1
Parley Hammon