Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular: $apply already in progress in IE11, but not in FF and Chrome

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?

like image 831
dendimiiii Avatar asked Aug 25 '16 08:08

dendimiiii


2 Answers

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;
            }
        });
    });
like image 55
Aruna Avatar answered Sep 28 '22 11:09

Aruna


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.

like image 30
s3raph86 Avatar answered Sep 28 '22 12:09

s3raph86