AngularJs:如何检查文件输入字段的更改?

289

我是Angular的新手。我正在尝试从HTML文件字段中读取上传文件路径,每当该字段发生“更改”时。如果我使用“onChange”,它可以工作,但当我按照Angular方式使用“ng-change”时,它无法工作。

<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() 没有被调用。Firebug也没有显示任何错误。


1
可能有帮助:https://dev59.com/F2Qn5IYBdhLWcg3wJ0UO - Marcel Gwerder
1
参见此链接:https://dev59.com/PmQn5IYBdhLWcg3wto9W - tliokos
15个回答

495

我写了一个小指令来监听文件输入的更改。

查看 JSFiddle

view.html:

<input type="file" custom-on-change="uploadFile">

控制器.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();
      });

    }
  };
});

4
如果每次选择不同的文件,这很有效。那么,是否可能在选择相同文件时也能监听到呢? - JDawg
13
很不幸,这里有一个非常不好的“bug” - 在customOnChange中,控制器没有被绑定为“this”,而是文件输入!这让我困扰了很长时间,我无法找到实际的错误所在。我还不确定如何正确设置“this”并调用它。 - Karel Bílek
4
今天起,无论是在Chrome还是Firefox上,小提琴对我都没用。 - seguso
2
这个答案存在缺陷,正如@KarelBílek所说。我无法解决它,决定使用angular-file-upload。 - Gunar Gessner
2
这绝对是正确的做法,非常有效。此外,在生产环境中为什么要使用调试功能呢? - Justin Kruse
显示剩余16条评论

249

文件上传控件没有绑定支持

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>

取代

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

你能试一下吗?

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

注意:这要求 angular 应用程序始终处于 调试模式。如果禁用调试模式,则此方法将无法在生产代码中正常工作。

在您的函数更改为

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

你能试试吗

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

以下是一个使用拖放上传文件的工作示例,可能会有所帮助。

http://jsfiddle.net/danielzen/utp7j/

AngularJS文件上传信息

ASP.Net中用于AngularJS文件上传的URL

https://github.com/geersch/AngularJSFileUpload

使用NodeJS进行进度跟踪的AngularJs本地多文件上传

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

ngUpload - 使用iframe上传文件的AngularJS服务

http://ngmodules.org/modules/ngUpload


2
对于(var i = 0; i < element.files.length; i++){ console.log(element.files[i]) }当您在Mozilla中循环遍历文件对象时,属性为“mozFullPath” 如果您发现我的答案有用,也可以点赞 - JQuery Guru
14
这种方法绕过了AngularJS的默认处理,因此不会调用$scope.$digest()(绑定不会更新)。之后调用'angular.element(this).scope().$digest()'或调用使用$apply的作用域方法。 - Ramon de Klein
120
这是一个糟糕的答案。调用angular.element(blah).scope()是一项仅供调试使用的功能,你只应该在调试时使用它。Angular不使用.scope功能,它只是为了帮助开发人员进行调试而存在的。当您构建应用程序并部署到生产环境时,应该关闭调试信息。这样可以极大地加快整个应用程序的速度!因此,编写依赖于element.scope()调用的代码是一个坏主意。不要这样做。有关创建自定义指令的答案是正确的方法。@JQueryGuru,您应该更新您的答案。由于它获得了最多的投票,人们会遵循这个错误的建议。 - frosty
7
当调试信息被禁用时,此解决方案将导致应用程序出现故障。在生产环境中禁用调试信息是官方建议 https://docs.angularjs.org/guide/production 。请参阅 http://blog.thoughtram.io/angularjs/2014/12/22/exploring-angular-1.3-disabling-debug-info.html。 - wiherek
4
不良解决方案...阅读其他有关“关闭调试”的评论...并查看sqren的解决方案...@0MV1 - dsdsdsdsd
显示剩余12条评论

43

这是对其他一些方法的改进,数据最终将存储在ng-model中,通常这是你想要的。

标记(只需创建一个名为data-file的属性,以便指令可以找到它)

<input
    data-file
    id="id_image" name="image"
    ng-model="my_image_model" type="file">

JavaScript

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

1
$scope.$apply(); 是必需的吗?我的 ng-change 事件似乎没有它也能触发。 - row1
1
@row1 如果您有其他需要在操作期间了解它的Angular事项,那么您只需要手动触发应用程序。 - Scott Sword

27

简单明了的方法是编写自己的指令来绑定“change”事件。需要注意的是,IE 9不支持FormData,因此您无法从更改事件中获取文件对象。

您可以使用ng-file-upload库,该库已经使用FileAPI polyfill支持IE,并简化将文件提交到服务器的过程。它使用指令来实现此功能。

<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:

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

这个例子已经过时了,请从这里的文档中获取新的例子。 - Gunar Gessner

26

我已经对@Stuart Axon的想法进行了扩展,为文件输入添加了双向绑定(即通过将模型值重置为null来重置输入):

我扩展了@Stuart Axon的想法,为文件输入添加了双向绑定(即允许通过将模型值重置为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("");
                }
            });
        }
    };
}]);

演示


谢谢@JLRishe。顺便提一下,实际上data-ng-click="vm.theFile=''"就可以了,不需要写到控制器方法中。 - Sergey

21

与其他好的答案类似,我编写了一个指令来解决这个问题,但是这个实现更加贴近于Angular附加事件的方式。

您可以像这样使用指令:

HTML

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

正如您所看到的,您可以将所选文件注入到事件处理程序中,就像将$event对象注入到任何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);
      }
    };
  }]);

我在这个问题上挣扎了几天,直到我尝试了你的答案。谢谢! - MikeT
我该如何向fileChange表达式传递额外的参数?我在ng-repeat中使用此指令,而传递给attrHandler$scope变量对于每个记录都是相同的。最佳解决方案是什么? - user1077685

18

这个指令也会将选定的文件一并传递:

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

    }
  };
});

HTML怎么使用:

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

在我的控制器中:

$scope.onFilesSelected = function(files) {
     console.log("files - " + files);
};

在你的控制器中,$scope.onFilesSelected是什么样子? - Balazs Nemeth
如果在Microsoft Edge浏览器中打开文件并尝试上传文件时,没有任何反应。你能帮忙吗? - Naveen Katakam
是的,它在其他浏览器上运行良好。我在Microsoft Edge中遇到了问题。@ingaham - Naveen Katakam
这正是我所需要的,谢谢。 - Pierre Smith

8
我建议创建一个指令。
<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});
                    });
                });
            }

        };
    }]);

这个指令可以被多次使用,它使用自己的作用域而不依赖于父级作用域。你也可以给处理函数传递一些参数。当你改变输入时,处理函数将被调用并附上当时活跃的作用域对象。 每次调用更改事件时,$apply会更新你的模型。


4
最简单的Angular jqLite版本。
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()">

如果在Microsoft Edge浏览器中打开文件并尝试上传文件时,没有任何反应。你能帮忙吗? - Naveen Katakam

2

“文件输入”指令的工作示例,可与ng-change一起使用1

为了让<input type=file>元素能够使用ng-change指令,它需要一个自定义指令,该指令可以与ng-model指令一起使用。

<input type="file" files-input ng-model="fileList" 
       ng-change="onInputChange()" multiple />

演示

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>


很好,但指令只被调用一次!如果我更改所选文件,则指令不会第二次被调用!您对此行为有任何想法吗? - hugsbrugs
DEMO没有那个问题。创建一个新的问题,并提供一个可重现的示例,以供读者检查。 - georgeawg
是的,它有这个问题,控制台日志“输入更改”只会显示一次,即使您多次更改所选文件! - hugsbrugs
经过测试,它在Chromium上运行良好,但在最新的Firefox版本上却不行...该死的浏览器兼容性!!! - hugsbrugs

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接