7

I got an

<input type="file" id="aircraftList" name="aircraftList" file-upload multiple/>

bound to a directive

angular.module("app.directives").directive('fileUpload', function () {
    return {
        scope: true,
        link: function (scope, el, attrs) {
            el.bind('change', function (event) {
                scope.$emit("fileSelected", { files: event.target.files, field: event.target.name });
            });
        }
    };
});

I catch this event in a controller:

$scope.$on("fileSelected", function (event, args) {
        $scope.$apply(function () {
            switch (args.field) {
                case "aircraftList":
                    self.attachments.aircraftList = args.files;
                    break;
                default:
                    break;
            }
        });
    });

For some reason this works perfectly well in Chrome and Firefox, but fails in IE11 with the following error:

Errormessage

If I dont put the $apply, chrome is not updating the view, but IE is. If I put the $apply, Chrome works perfect and IE breaks.

Anyone knows what and why it goes wrong here?

dendimiiii
  • 1,659
  • 3
  • 15
  • 26

6 Answers6

6

Actually, chrome and FF javascript engines are very fast when compared to IE11 javascript engine.

Hence, when the $scope.$on("fileSelected" is triggered in chrome & FF, the previous $digest loop will be completed at the time of $scope.$apply is executed and hence no errors. As there is no $digest cycle is running at this stage, we need another $digest cycle to update the view with help of $scope.$apply and without this, view won't be updated.

As IE is comparatively slow on the same scenario above, $scope.$apply is throwing error as there is one $digest loop is running currently. Hence, without $scope.$apply, the view will get updated with help of the running $digest cycle.

When we use $timeout as said by other users, it will start executed once the current $digest cycle is completed and making sure to update the view with another $digest loop.

Hope it will clarify you :-)

$scope.$on("fileSelected", function (event, args) {
        $timeout(function () {
            switch (args.field) {
                case "aircraftList":
                    self.attachments.aircraftList = args.files;
                    break;
                default:
                    break;
            }
        });
    });
Aruna
  • 11,959
  • 3
  • 28
  • 42
3

i don't know why both the browsers are behaving diffrently. But I know to get rid of this error and make it work

You have used $apply(). If you get $rootScope:inprog error, it means already a digest cycle is running. To avoid this error wrap your $apply function under a timeout condition.

like this,

$scope.$on("fileSelected", function (event, args) {
$timeout(function () {
$scope.$apply(function () {
        switch (args.field) {
            case "aircraftList":
                self.attachments.aircraftList = args.files;
                break;
            default:
                break;
        }
    });
  },500);
});

I hope this works for you.

Vivek Verma
  • 293
  • 3
  • 16
  • This indeed works for me, even without the 500 ms delay. I would still like to know why this behaviour is happening though. – dendimiiii Nov 04 '16 at 14:37
  • 2
    You can remove the `$scope.$apply` in `$timeout` because `$timeout` is triggering `$digest` internally. Generally it is safer to use `$timeout` when you need to use `$scope.$apply`. – Icycool Nov 09 '16 at 13:24
3

You stuck into this issue as your code try to trigger digest cycle before one got completed and that you are facing only in IE probably because of slow nature of IE. so my idea is to use $scope.$evalAsync

$scope.$evalAsync(function () {
            switch (args.field) {
                case "aircraftList":
                    self.attachments.aircraftList = args.files;
                    break;
                default:
                    break;
            }
        });

$scope.$evalAsync will adds the supplied function to a queue that will be drained at the beginning of the next loop in a digest cycle.

I hope this work for you.

Thanks

Mahipat
  • 611
  • 1
  • 5
  • 12
3

Firstly, you're calling $apply from within an already executing $digest cycle. Chrome/FF may be fine for you, but that's really down to luck on your part. Really on this you're at the mercy of the user's PC performance. Angular will always trigger off it's own $digest cycle whenever it is transmitting events. Your $scope.$emit will be triggering $digest here.

You've got a few problems here though which are going to be tying everything up in knots, and will cause further problems of this kind. Normally there should be no need for you to trigger a $digest cycle unless you are responding to events triggered from outside Angular.

Your directive file-uploader seems far too dependent on your view model - it's even telling the controller which field it should be storing the returned data in. Rememember, that's the controllers job! I've changed your code around a little to ensure that there's no need to have two simultaneous $apply cycles, which eliminates your problem, and also to tidy up the code a little.

I've changed the directive to use two-way data-binding instead of emitting events via the rootscope - big improvement to performance, and encapsulation of functionality.

app.directive('testUploader', [function() {
    return {
        scope: {
            ngModel: '='
        },
        link: function(scope, el) {
            el.bind('change', function(event) {
                if (event.target.files && event.target.files.length > 0) {
                    angular.forEach(event.target.files, function (newFile) {
                        scope.ngModel.push(newFile);
                    });
                    scope.$apply();
                }
            });
        }
    };
}]);

This vastly simplifies the controller which now has no need to handle client events - it simply mediates between the view model and any underlying data model up on your server

app.controller("testctrl", ['$scope', function($scope) {    
    $scope.data = {
        aircraftList: []
    };
}]);

See working JSFiddle here: https://jsfiddle.net/z2yLad92/27/

Hope this helps.

s3raph86
  • 556
  • 4
  • 17
1

You can apply scope by checking first

(!$scope.$$phase) ? $scope.$apply() : null;

It will not apply scope if it is already in progress.

kailash yogeshwar
  • 836
  • 1
  • 9
  • 26
0

I don't think any of the others answers here (at time of posting) are correct.

No doubt there is some sort of timing issue or similar between Chrome/FF vs IE11, but that isn't the fundamental problem here.

The fundamental problem is this:

el.bind('change', function (event) {
    scope.$emit("fileSelected", { files: event.target.files, field: event.target.name });
});

You shouldn't be emitting an AngularJS event, when you're not in an AngularJS digest cycle.

The code should be this:

el.bind('change', function (event) {
    scope.$apply(function() {
        scope.$emit("fileSelected", { files: event.target.files, field: event.target.name });
    });
});

You can then remove the $scope.$apply() call from your second piece of code.

Dan King
  • 3,412
  • 5
  • 24
  • 23