用于`<input type =“file”/>`的ng-model(带指令DEMO)

时间:2013-06-12 10:18:07

标签: angularjs angularjs-ng-model angularjs-fileupload

我尝试在输入标签上使用ng-model,类型为file:

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

但是在选择文件后,在控制器中,$ scope.vm.uploadme仍未定义。

如何在控制器中获取所选文件?

12 个答案:

答案 0 :(得分:319)

我使用指令创建了一个解决方法:

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

输入标签变为:

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

或者只需要文件定义:

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

答案 1 :(得分:53)

我使用这个指令:

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

并像这样调用它:

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

属性(editItem.editItem._attachments_uri.image)将填充您选择作为data-uri(!)的文件的内容。

请注意,此脚本不会上传任何内容。它只会使用data-uri(base64)编码文件的内容来填充模型。

在这里查看一个有效的演示: http://plnkr.co/CMiHKv2BEidM9SShm9Vv

答案 2 :(得分:27)

这是@ endy-tjahjono解决方案的补遗。

我最终无法从示波器中获取 uploadme 的值。即使HTML中的 uploadme 被指令明显更新,我仍然无法通过$ scope.uploadme访问其值。不过,我能够从范围设置其值。神秘,对..?

事实证明,指令创建了子范围,子范围有自己的 uploadme

解决方案是使用对象而不是基元来保存 uploadme 的值。

在控制器中我有:

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

并在HTML中:

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

指令没有变化。

现在,这一切都像预期的那样有效。我可以使用$ scope.uploadme从我的控制器中获取 uploadme.src 的值。

答案 3 :(得分:27)

ng-model

一起使用的指令的工作演示

如何启用<input type="file">以使用ng-model

核心ng-model指令不适用于<input type="file"开箱即用。

此自定义指令启用ng-model,并且具有使ng-changeng-requiredng-form指令与<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>

答案 4 :(得分:4)

这是一个稍微修改过的版本,允许您在范围中指定属性的名称,就像使用ng-model时一样:

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

指令:

.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'

    };
});

答案 5 :(得分:3)

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

答案 6 :(得分:3)

对于使用lodash或下划线输入的多个文件:

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

答案 7 :(得分:2)

我必须在多个输入上做同样的事情,所以我更新了@Endy Tjahjono方法。 它返回一个包含所有已读文件的数组。

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

      }
    }
  });

答案 8 :(得分:1)

我必须修改Endy的指令,以便我可以获得Last Modified,lastModifiedDate,名称,大小,类型和数据,以及能够获取文件数组。对于那些需要这些额外功能的人,请点击此处。

更新: 我发现了一个错误,如果您选择文件,然后再次选择但取消,则文件永远不会被取消选择,就像它出现一样。所以我更新了我的代码以解决这个问题。

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

            }
        }
    });

答案 9 :(得分:0)

如果您想要更优雅/更集成的东西,可以使用decorator扩展input指令,并支持type=file。需要牢记的主要警告是,由于IE9未实现File API,因此该方法在IE9中将不起作用。在IE9或更早版本中,根本不可能使用JavaScript通过XHR上传二进制数据,而无论其类型是什么(在{9}中使用ActiveXObject来访问本地文件系统根本不算是使用ActiveX只是在问安全性问题)。

此精确方法也需要AngularJS 1.4.x或更高版本,但是您可以使其适应使用$provide.decorator而不是angular.Module.decorator-我写了this gist来演示如何做符合John Papa's AngularJS style guide的情况:

(function() {
    'use strict';

    /**
    * @ngdoc input
    * @name input[file]
    *
    * @description
    * Adds very basic support for ngModel to `input[type=file]` fields.
    *
    * Requires AngularJS 1.4.x or later. Does not support Internet Explorer 9 - the browser's
    * implementation of `HTMLInputElement` must have a `files` property for file inputs.
    *
    * @param {string} ngModel
    *  Assignable AngularJS expression to data-bind to. The data-bound object will be an instance
    *  of {@link https://developer.mozilla.org/en-US/docs/Web/API/FileList `FileList`}.
    * @param {string=} name Property name of the form under which the control is published.
    * @param {string=} ngChange
    *  AngularJS expression to be executed when input changes due to user interaction with the
    *  input element.
    */
    angular
        .module('yourModuleNameHere')
        .decorator('inputDirective', myInputFileDecorator);

    myInputFileDecorator.$inject = ['$delegate', '$browser', '$sniffer', '$filter', '$parse'];
    function myInputFileDecorator($delegate, $browser, $sniffer, $filter, $parse) {
        var inputDirective = $delegate[0],
            preLink = inputDirective.link.pre;

        inputDirective.link.pre = function (scope, element, attr, ctrl) {
            if (ctrl[0]) {
                if (angular.lowercase(attr.type) === 'file') {
                    fileInputType(
                        scope, element, attr, ctrl[0], $sniffer, $browser, $filter, $parse);
                } else {
                    preLink.apply(this, arguments);
                }
            }
        };

        return $delegate;
    }

    function fileInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter, $parse) {
        element.on('change', function (ev) {
            if (angular.isDefined(element[0].files)) {
                ctrl.$setViewValue(element[0].files, ev && ev.type);
            }
        })

        ctrl.$isEmpty = function (value) {
            return !value || value.length === 0;
        };
    }
})();

为什么这不是首先完成的? AngularJS support is intended to reach only as far back as IE9. If you disagree with this decision and think they should have just put this in anyway, then jump the wagon to Angular 2+ because better modern support is literally why Angular 2 exists.

  

问题是(如前所述)没有文件api   考虑到我们的核心,正确执行此操作的支持是不可行的   基线是IE9,然后填充这些东西是不可能的   为核心。

     

另外尝试以某种方式处理此输入   跨浏览器兼容只会使第三方解决方案更加困难,   现在必须解决/禁用/解决核心解决方案。

     

...

     

我将在关闭#1236时将其关闭。 Angular 2正在   构建以支持现代浏览器,并通过该文件支持将   容易获得。

答案 10 :(得分:0)

或者,您可以获取输入并设置 onchange 函数:

<input type="file" id="myFileInput" />
document.getElementById("myFileInput").onchange = function (event) {
   console.log(event.target.files);                        
};

答案 11 :(得分:-1)

试试这个,这对我来说是角色JS

    let fileToUpload = `${documentLocation}/${documentType}.pdf`;
    let absoluteFilePath = path.resolve(__dirname, fileToUpload);
    console.log(`Uploading document ${absoluteFilePath}`);
    element.all(by.css("input[type='file']")).sendKeys(absoluteFilePath);