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:
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?
Actually, chrome
and FF
javascript engines are very fast when compared to IE11
javascript engine.
Hence, when the
$scope.$on("fileSelected"
is triggered inchrome
&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;
}
});
});
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With