`<input type="file"/>` 的 ng-model(带有指令 DEMO)

Posted

技术标签:

【中文标题】`<input type="file"/>` 的 ng-model(带有指令 DEMO)【英文标题】:ng-model for `<input type="file"/>` (with directive DEMO) 【发布时间】:2013-06-08 10:01:48 【问题描述】:

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

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

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

如何在我的控制器中获取选定的文件?

【问题讨论】:

参见***.com/a/17923521/135114,尤其是jsfiddle.net/danielzen/utp7j在线引用的示例 相信大家在使用ngModel时总是需要在html元素上指定name属性。 【参考方案1】:

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

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

【讨论】:

我在 img 标签中使用 uploadme 作为 src,所以我可以看到它是由指令设置的。但是,如果我尝试使用 $scope.uploadme 从控制器中获取它,它是“未定义的”。不过,我可以从控制器设置uploadme。例如,$scope.uploadme="*" 使图像消失。 问题在于该指令创建了一个 childScope,并在该范围内设置了 uploadme。原始(父)范围也有一个uploadme,不受childScope影响。我可以从任一范围更新 HTML 中的 uploadme。有没有办法完全避免创建 childScope? @AlexC 好吧,问题是关于 ng-model 不起作用,而不是关于上传文件:) 当时我不需要上传文件。但最近我学会了如何从这个egghead.io tutorial上传文件。 别忘了 $scope.$on('$destory', function() element.unbind("change"); 我有一个问题....与普通的javascript和html相比,这种方式不是太复杂了吗?说真的,你真的需要了解 AngularJS 才能达到这个解决方案......而且我似乎可以对 javascript 事件做同样的事情。为什么要用 Angular 的方式而不是普通的 JS 方式呢?【参考方案2】:

我使用这个指令:

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 (!) 的文件的内容。

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

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

【讨论】:

看起来很有希望,你能解释一下代码背后的逻辑,并评论浏览器兼容性(主要是IE和非fileAPI浏览器)吗? 另外,据我所知,如果我将 AJAX 请求的 content-type 标头设置为 undefined,并尝试将这样的字段发送到服务器,Angular 将上传它,假设浏览器支持fileAPI,对吗? @OlegTikhonov 你不正确!这个脚本不会发送任何东西。它将读取您选择的文件作为 Base64 字符串并使用该字符串更新您的模型。 @Elmer 是的,我明白,我的意思是通过发送一个包含文件字段的表单(FileAPI 对象中用户机器中文件的相对路径),您可以使通过将内容类型标头设置为未定义,通过 XHR 请求角度上传文件 覆盖ngModel$render函数的目的是什么?【参考方案3】:

如何使&lt;input type="file"&gt;ng-model 一起工作

使用ng-model 的指令的工作演示

核心ng-model 指令不适用于开箱即用的&lt;input type="file"&gt;

此自定义指令启用ng-model,并具有使ng-changeng-requiredng-form 指令与&lt;input type="file"&gt; 一起使用的额外好处。

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 为 undefined **** if(files.length > 0) ngModel.$setViewValue(files); else ngModel.$setViewValue(undefined); 如何获取文件的数据?我们可以使用的其他属性是什么,例如 file.name @AdarshSingh 见MDN Web API Reference - File 和MDN Reference - Using files from web applications【参考方案4】:

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

【讨论】:

是的,就是这样。 我确认同样的经历;非常好的调试和解释。我不确定为什么该指令会创建自己的范围。 或者,内联声明:$scope.uploadme = src: '' 【参考方案5】:

我创建了一个指令并在 bower 上注册。

此库将帮助您对输入文件进行建模,不仅返回文件数据,还返回文件 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?我尝试使用它,但在调试时未定义。 干得好 yozawiratama !它运作良好。而@GujaratSantana,如果 那么只需使用 $scope.myDocument.name 或一般 $scope.myDocument. 其中属性是其中之一[“lastModified”、“lastModifiedDate”、“名称”、“大小”、“类型”、“数据”] 无法通过 bower 安装 用户如何上传多个文件? reader.readAsDataURL 方法已过时。现代代码使用URL.create​ObjectURL()。【参考方案6】:

这是一个稍作修改的版本,允许您在范围内指定属性的名称,就像您对 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'

    ;
);

【讨论】:

【参考方案7】:

对于使用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);
                );
            );
        
    
]);

【讨论】:

【参考方案8】:

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

【讨论】:

感谢返回 file 对象。将其转换为 DataURL 的其他指令使想要上传文件的控制器变得困难。【参考方案9】:

我必须在多个输入上做同样的事情,所以我更新了@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] );
          
        );

      
    
  );

【讨论】:

【参考方案10】:

我必须修改 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] );
                    
                );

            
        
    );

【讨论】:

【参考方案11】:

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

这个确切的方法还需要 AngularJS 1.4.x 或更高版本,但您可以调整它以使用 $provide.decorator 而不是 angular.Module.decorator - 我写了 this gist 来演示如何在遵守 @987654323 的同时做到这一点@:

(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 并且填充这些东西是不可能的 核心。

另外尝试以不 跨浏览器兼容只会让第 3 方解决方案更难, 现在必须对抗/禁用/解决核心解决方案。

...

我将在关闭 #1236 时关闭它。 Angular 2 正在 构建以支持现代浏览器并使用该文件支持将 很容易获得。

【讨论】:

【参考方案12】:

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

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

【讨论】:

【参考方案13】:

试试这个,这在 Angular 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);

【讨论】:

以上是关于`<input type="file"/>` 的 ng-model(带有指令 DEMO)的主要内容,如果未能解决你的问题,请参考以下文章

input type=file文件选择

<input type="file">,输入的信息怎样用jquery获取?

自定义input[type="radio"]的样式

自定义input[type="radio"]的样式

为啥 textarea 不是 input[type="textarea"]?

<input type="text">文本输人框