Angular seems to get tripped up by the 'open' state of Chrome's (possibly other browsers too) XMLHttpRequest during the $digest phase while copying it. When the XMLHttpRequest.status field is accessed it raises an exception. My actual use-case doesn't bind to the status field; the XMLHttpRequest object is just one member of another object in an array which I $watch.
So,
a) Should Angular be smarter about such cases and ignore the exception?
b) Is there a way to have Angular ignore fields or objects when it is digesting containing objects?
c) Other workaround suggestions?
Here's an example. This HTML+Javascript (JSFiddle):
<div ng-app ng-controller="TestCtrl">
<h2>XMLHttpRequest.status: {{ xhr.status }}</h2>
</div>
function TestCtrl($scope) {
$scope.xhr = new XMLHttpRequest();
$scope.xhr.open('POST', 'http://localhost:8000/api/medias', true);
}
Results in this error:
Error: INVALID_STATE_ERR: DOM Exception 11
Error: An attempt was made to use an object that is not, or is no longer, usable.
at eval (eval at Jb (http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.0.3/angular.min.js:74:389), <anonymous>:16:4)
at d.fn.x.assign (http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.0.3/angular.min.js:63:120)
at Object.j (http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.0.3/angular.min.js:52:126)
at Object.e.$digest (http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.0.3/angular.min.js:84:286)
at Object.e.$apply (http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.0.3/angular.min.js:86:469)
at http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.0.3/angular.min.js:15:396
at Object.d [as invoke] (http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.0.3/angular.min.js:26:401)
at pb (http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.0.3/angular.min.js:15:317)
at jc (http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.0.3/angular.min.js:15:178)
at http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.0.3/angular.min.js:159:424 angular.min.js:60
You need to use the $http service. (Example 99% lifted from the angular documentation example)
Controller:
function TestCtrl($http, $scope) {
$scope.xhr = {status : undefined};
$http.post('http://localhost:8000/api/medias', {params:{'go':'here'}})
.success(function(data, status) {
$scope.xhr.status = status;
})
.error(function(data, status) {
$scope.xhr.status = status;
});
}
EDIT(not tested): Controller using XMLHttpRequest:
using event listeners to call $apply
function TestCtrl($http, $scope) {
$scope.xhr = {status : undefined};
var xhr = new XMLHttpRequest();
xhr.addEventListener('progress', function(event) {
var progress = event['something']//get what you need from the event..
$scope.$apply(function() {
$scope.xhr.progress = progress;
});
}, false);
xhr.open('POST', 'http://localhost:8000/api/medias', true);
}
Template:
<div ng-app ng-controller="TestCtrl">
<h2>XMLHttpRequest.status: {{ xhr.status }}</h2>
<h2>XMLHttpRequest.status: {{ xhr.progress}}</h2>
</div>
Example:
To part b of my question:
b) Is there a way to have Angular ignore fields or objects when it is digesting containing objects?
The answer is yes, wrap the field/object with a function, e.g.:
$scope.getXhr = function () { return xhr; }
And Angular will ignore it when $digesting. Of course this presumes you want/need it to be ignored. Changes to that field/object will not trigger watchers.