ng-model用于“ <输入类型=“文件” />”(带有指令DEMO)


281

我试图在类型文件的输入标签上使用ng-model:

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

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

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


3
请参阅stackoverflow.com/a/17923521/135114,尤其是在线引用的示例,网址为jsfiddle.net/danielzen/utp7j
Daryn

我相信您在使用ngModel时总是需要在html元素上指定name属性。
2014年

Answers:


326

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

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

4
我在img标签中将uploadme用作src,因此我可以看到它已由指令设置。但是,如果我尝试使用$ scope.uploadme从控制器中获取它,则它是“未定义的”。不过,我可以从控制器设置uploadme。例如,$ scope.uploadme =“ *”使图像消失。
Per Quested Aronsson

5
问题是该指令创建了childScope,并在该范围内设置了uploadme。原始(父)范围还具有一个uploadme,不受childScope的影响。我可以从任何一个范围更新HTML中的uploadme。有没有办法完全避免创建childScope?
Per Quested Aronsson

4
@AlexC很好,问题是关于ng-model不起作用,而不是关于上传文件:)那时我不需要上传文件。但是最近我从这个egghead.io教程中学习了如何上传文件。
Endy Tjahjono 2015年

10
不要忘记$ scope。$ on('$ destory',function(){element.unbind(“ change”);}
Nawlbergs

2
我有一个问题。...与普通的javascript和html相比,这种方法是否过于复杂?认真地讲,您确实需要了解AngularJS才能实现此解决方案……而且看来我可以通过javascript事件来做到这一点。为什么用Angular而不是普通的JS方式呢?
AFP_555

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)将使用您选择作为数据uri(!)的文件的内容填充。

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

在此处查看工作示例:http : //plnkr.co/CMiHKv2BEidM9SShm9Vv


4
看起来很有希望,您能否解释一下代码背后的逻辑,并评论一下浏览器的兼容性(主要是IE和非fileAPI浏览器)?
Oleg Belousov

另外,据我所知,如果我将AJAX请求的content-type标头设置为undefined,并尝试将此类字段发送到服务器,则假定浏览器支持fileAPI,则angular将上传该字段,我正确吗?
Oleg Belousov

@OlegTikhonov你不正确!该脚本不会发送任何内容。它将读取您选择为Base64字符串的文件,并使用该字符串更新模型。
Elmer

@Elmer是的,我的意思是,通过发送包含文件字段的表单(FileAPI对象中用户计算机中文件的相对路径),您可以通过XHR请求使文件有角度地上传通过将内容类型标题设置为undefined
Oleg Belousov

1
什么是overwritting目的ngModel$render功能?
sp00m '16

39

如何启用<input type="file">与之合作ng-model

与之配合使用的指令的工作演示 ng-model

开箱即ng-model用的核心指令不起作用<input type="file">

这个自定义指令启用ng-model并具有使额外的好处ng-changeng-required以及ng-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>


您可以使用条件来检查是否没有选定的文件,ng-model是未定义的**** if(files.length> 0){ngModel。$ setViewValue(files); } else {ngModel。$ setViewValue(undefined); }
Farshad Kazemi

如何获取文件数据?还有其他可以使用的属性,例如{{file.name}}
Adarsh Singh,


26

这是@ 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的值。


1
是的,就是这样。
Per Quested Aronsson

1
我确认同样的经历;非常好的调试和说明。我不确定指令为什么要创建自己的作用域。
亚当·凯西

或者,内联声明:$scope.uploadme = { src: '' }
maxathousand

9

大家好,我创建了一条指令并在凉亭上注册。

这个库将帮助您建模输入文件,不仅返回文件数据,而且还返回文件dataurl或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/

希望能帮到您


如何使用$ scope使用它?我尝试使用它,但在调试时未定义。
古吉拉特邦桑塔纳2015年

1
尼斯工作yozawiratama!效果很好。@GujaratSantana,如果<input type =“ file” ng-file-model =“ myDocument” />,则只需使用$ scope.myDocument.name或一般的$ scope.myDocument。<any property>,其中property是其中之一[“ lastModified”,“ lastModifiedDate”,“名称”,“大小”,“类型”,“数据”]
Jay Dharmendra Solanki

无法通过凉亭安装
Pimgd'Sep 9'16

用户如何上传多个文件?
aldrien.h

reader.readAsDataURL方法已过时。现代代码使用URL.createObjectURL()
georgeawg

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'

    };
});

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

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

返回文件对象的荣誉。将其转换为DataURL的其他指令使想要上载文件的控制器很难。
georgeawg

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

      }
    }
  });

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

            }
        }
    });

1

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

这个确切的方法还需要AngularJS 1.4.x或更高版本,但是您可能可以改编它以使用,$provide.decorator而不是angular.Module.decorator-我写了这个要点来演示如何遵循John Papa的AngularJS样式指南

(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支持仅可追溯到IE9。如果您不同意这个决定,并认为他们应该以某种方式解决这个问题,那么就选择Angular 2+,因为更好的现代支持实际上就是Angular 2存在的原因。

问题是(如前所述)如果没有文件api支持,那么对于内核来说,这样做是不可行的,因为我们的基准是IE9,而对于内核来说,进行这种填充是不可能的。

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

...

我要关闭这个就像我们关闭#1236一样。正在构建Angular 2以支持现代浏览器,并且可以轻松使用该文件支持。


-2

试试这个,这对我有帮助

    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);
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.