AngularJs: How to check for changes in file input fields?

I am new to angular. I am trying to read the uploaded file path from HTML 'file' field whenever a 'change' happens on this field. If i use 'onChange' it works but when i use it angular way using 'ng-change' it doesn't work.

<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() is never calling. Firebug also doesn't show any error.


Solution 1:

I made a small directive to listen for file input changes.

View JSFiddle

view.html:

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

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

    }
  };
});

Solution 2:

No binding support for File Upload control

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>

instead of

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

can you try

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

Note: this requires the angular application to always be in debug mode. This will not work in production code if debug mode is disabled.

and in your function changes instead of

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

can you try

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

Below is one working example of file upload with drag drop file upload may be helpful http://jsfiddle.net/danielzen/utp7j/

Angular File Upload Information

URL for AngularJS File Upload in ASP.Net

https://github.com/geersch/AngularJSFileUpload

AngularJs native multi-file upload with progress with NodeJS

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

ngUpload - An AngularJS Service for uploading files using iframe

http://ngmodules.org/modules/ngUpload

Solution 3:

This is a refinement of some of the other ones around, the data will end up in an ng-model, which is normally what you want.

Markup (just make an attribute data-file so the directive can find it)

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

JS

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

Solution 4:

The clean way is to write your own directive to bind to "change" event. Just to let you know IE9 does not support FormData so you cannot really get the file object from the change event.

You can use ng-file-upload library which already supports IE with FileAPI polyfill and simplify the posting the file to the server. It uses a directive to achieve this.

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

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

Solution 5:

I've expanded on @Stuart Axon's idea to add two-way binding for the file input (i.e. allow resetting the input by resetting the model value back to 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("");
                }
            });
        }
    };
}]);

Demo