1

I've got a weird one (for me at least) that hopefully someone will be able to help with. Maybe I'm just thinking about this the wrong way or something. Attempting to work with files in javascript is completely new to me.

I've got a directive whose template looks like this:

<input type="file" ng-file-select ng-model="files">
<button ng-click="onFileSelect()">Upload</button>

In the controller for the directive, I am doing this:

$scope.onFileSelect = function() {
  reader = new FileReader();
  reader.onload = function() {
    $scope.file_contents = this.result;
  };
  reader.readAsArrayBuffer($scope.files[0]);

//debugger;
  $scope.$watch('file_contents',function(file_contents) {
    console.log(file_contents);
    if (angular.isDefined(file_contents)) {
      ... bunch of code attempting to deal with file_contents ...
    }
  });
};

I choose a file, then click the Upload button. The console.log of (file_contents) is undefined. Only when I click the button the second time, does it have a value.

If I uncomment the debugger, $scope.file_contents has a value by the time I'm checking it.

So, file_contents takes a moment to get set (which is expected), but the $watch never seems to notice? Is there some strange reason for this? Does $watch not work with FileReader objects?

Edit 1:

Okay. Here's some more information. Following PSL's advice, I now have the code as such:

$scope.onFileSelect = function() {
  reader = new FileReader();
  reader.onload = function() {
    file_contents = this.result;
    upload_file($scope.files[0],file_contents);
  };
   reader.readAsArrayBuffer($scope.files[0]);
};

upload_file = function(file,file_contents) {
  <lots of cool file_contents-y stuff>
};

Why am I still getting $digest errors? I have no $watches any more. I don't do anything with $scope within upload_file. There's no stack trace from the $digest error to help me, either. All I get is:

Error: [$rootScope:inprog] $digest already in progress
http://errors.angularjs.org/1.3.0-beta.10/$rootScope/inprog?p0=%24digest

What's doing this?

0

2 Answers 2

7

Watch never seems to notice because you are updating the scope outside of angular context. You would need to manually invoke digest cycle using scope.$apply() or any other way like using a $timeout etc inside onload async function. and better move the watch outside of the onFileSelect method otherwise you will keep on adding up watched every upload click.

Try:

$scope.onFileSelect = function() {
  reader = new FileReader();
  reader.onload = function() {
    $scope.file_contents = this.result;
    $scope.$apply(); /<-- here
  };
  reader.readAsArrayBuffer($scope.files[0]);

};

$scope.$watch('file_contents',function(file_contents) {
    console.log(file_contents);
    if (angular.isDefined(file_contents)) {
      ... bunch of code attempting to deal with file_contents ...
    }
});

Not sure about the complete scenario but you probabaly don't even need to create a watch just wrap the file processing inside a method and invoke the method from onload and perform a scope.$apply(). Reason why your watch executes for the first time is because it always runs once it is set up inorder to kick off dirty check and by that time async onload has not set any value on the scope and even if it does set up digest cycle is not aware of it.

Sign up to request clarification or add additional context in comments.

8 Comments

This is the way that is working. HOWEVER, I am getting these stupid $digest errors. Can I ignore them? I have tried 1,000 different ways to get rid of them and NONE of them work. $timeout is not a solution. If for some reason I decide to click Upload, wait for it to upload, then click upload again, at some point, I get $digest errors. I am going to post a followup below with more code. I am not doing ANYTHING with $scope any more and cannot figure out why it's still giving me the problem.
@JimMacKenzie You should not get digest error, do you have a demo?
I've never set up a jsfiddle or similar. It might take a while.
You do not have scope.$apply right?If so remove it and try. I dont know what all plugins youa re using. SO have to assume things
No. Nowhere in my code am I using $scope.$apply. I never even knew about it until you mentioned it. I've updated the question with my new code. There's no $scope.$apply.
|
0

There is a much nicer approach to this.. You define a directive like this..

//directive.js
(function() {
    angular.module('<yourApp>').
    directive('fileread', ['$parse', function($parse){
        // Runs during compile
        return {
            restrict: 'A',
            link: function($scope, iElm, iAttrs, controller) {

                var model = $parse(iAttrs.fileread);
                var modelSetter = model.assign;

                iElm.bind('change', function(){
                    $scope.$apply(function(){
                        modelSetter($scope, iElm[0].files[0]);

                    });
                });     
            }
        };
    }]);
})();

Then in your controller(or some other directive) you create a model like this..

//controller.js
...
$scope.photo.data = null;
...

$scope.photo.upload = function() {
    fd = new FormData();
    fd.append('<name_of_form_field_the_photo_should_be_sent_as>', $scope.photo.data);

$http.post('<url>', fd, {
     transformRequest: angular.identity,
     headers: {'Content-Type': undefined}
})

and finally in you html.

<input type = 'file' fileread = "photo.data">
<button ng-click = "photo.upload"> </button>

I hope it helps.

1 Comment

Unfortunately, no. I have to send multipart/mixed and there is no way to do it without creating my own code to send the data. I cannot use 'Content-type': undefined, as it defaults to multipart/form-data.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.