AngularJS ngResource $save feedback - angularjs

Hello I am using ngResource $save method and I get two different behaviours, I don't understand why
First I'm using it in this way:
$scope.user = new User($scope.user);
$scope.user.$save(function () {
$window.location.href = //redirection here;
}, function (response) {
$scope.form.addErrors(response.data.errors);
});
Then I have another controller when I'm doing a similar operation, but even getting 404 or 422 errors from the server the first callback is executed and the errors callback is ignored.
Does anyone have any idea of this? I've been searching in Google for hours trying to find more documentation about $save but I'm still stuck with this problem.
Thank you.

Well, the problem was on an interceptor I am using to detect 401 (unauthorized errors)
here is the interceptor, notice that you must return $q.reject(response) otherwise the other callbacks are not called (in my case the error callback in ngResource.$save)
MyApp.config(function ($httpProvider) {
$httpProvider.interceptors.push(function($window, $q) {
return {
'responseError': function(response) {
if (response.status == 401) { // Unathorized
$window.location.href = 'index.html';
}
// return response; <-- I was doing this before cancelling all errors
return $q.reject(response);
}
};
});
});

Related

Fail-Proof UI-Router Resolve w/ Restangular?

I am attempting to implement UI-router resolves which will return a result even if the API call fails. Our app has back-end permissions, and if the call fails, I still need to show nested pages (which won't load if a resolve calls fails normally, unless it is wrapped into a $q promise. I implement it like this with $http:
resolve: {
kittens: ["$q", "$timeout","$http", function ($q, $timeout, $http,) {
var url = "some api url"
return $http.get (url)
.then(function(result){ return {status:true , data: result} },
function(){ return {status:false} }); //on failure, return this
}],
}
The above works perfectly - it returns what I need on both call success and failure, however, it seems to fail if I try it with Restangular, the code below works fine:
kittens: function (Restangular) {
return Restangular
.one('stuff', 999999999999)
.all('stuffInStuff').getList()
.then(function (result) {
return result;
});
},
but if I try this with the above:
.then(function (result) {
return result;
}, function(error){return error})
the failure doesn't return anything and the controller isn't instantiated. I don't understand why this happens. I thought both $http.get() and Restangular.one().all().getList() (as an example) are both equivalent promises, that either return a result or fail. What's the difference? How do I provide a resolve value on call fail with Restangular?
Edit: Btw, I did read this post, and I understand that if a UI-router resolve isn't wrapped, it fails if the promise is rejected, but I don't seem to fully get how to approach it with Restanagular...
edit 3: This fails as well:
.then(function (result) {
return result;
})
.catch(function (error) {
return error;
})
I think you have two ways ,
1.) Dont return restangular instance inside resolve of ui router, because you are returning promise which is rejected. you cant bypass this rejection natively. When you return Restangular.one().get() and it fails, your resolve fails. if you dont return instance, resolve will get resolved even when your request is still loading(which i think you dont want and you want wait for success/error)
second , for me correct way is this.
resolve: {
kittens: ["$q", "Restangular", function ($q, Restangular) {
var deferred = $q.defer();
Restangular.one('some-api').get().then(function(data) {
deferred.resolve(data);
}, function(err) {
deferred.resolve(err);
});
return deferred.promise;
}],
}
in this case, resolve will be resolved everytime and you will get transition even when rest call will fail

AngularJS ngResource: How to always use the same catch function?

Every time I hit the servers using any $resource I want to show the same alert to my users whenever it fails.
Today, it looks like:
function tryAgain() { alert("try again") }
myResource.query().$promise.catch(tryAgain);
myResource.update(...).$promise.catch(tryAgain);
myResource.delete(...).$promise.catch(tryAgain);
otherResource.query().$promise.catch(tryAgain);
Is there a way to configure the default error handling function for ngResource? I'm looking for something like:
$resource.somethingMagicHere.defaults.catch(tryAgain);
You can use an interceptor in your app.config() section. This will catch all response errors originating from $http which $resource uses.
$httpProvider.interceptors.push(function($q) {
return {
'responseError': function(response) {
if (response.status == 401) {
// Handle 401 error code
}
if (response.status == 500) {
// Handle 500 error code
}
// Always reject (or resolve) the deferred you're given
return $q.reject(response);
}
};
});
The #kba answer helped me find the path. The following article made me understand it:
http://www.webdeveasy.com/interceptors-in-angularjs-and-useful-examples/
Just declare it once and reuse:
var defaultErrorHandler = function() {
alert("try again")
}
myResource.query(...).$promise.catch(defaultErrorHandler());
myResource.update(...).$promise.catch(defaultErrorHandler());
...

Delay an angular.js $http service

I have some angular factories for making ajax calls towards legacy ASP.NET .asmx web services like so:
module.factory('productService', ["$http",
function ($http) {
return {
getSpecialProducts: function (data) {
return $http.post('/ajax/Products.asmx/GetSpecialProducs', data);
}
}
} ]);
I'm testing on a local network so response times are "too" good. Is there a smart way of delaying the $http a couple of seconds from making the call to simulate a bad connection?
Or do I need to wrap all calls to the factory methods in a $timeout ?
$timeout(function() {
productService.getSpecialProducs(data).success(success).error(error);
}, $scope.MOCK_ajaxDelay);
Interesting question!
As you mentioned yourself, $timeout is the most logical choice for a delayed call. Instead of having $timeout calls everywhere, you could push a response interceptor that wraps the $http promise in a $timeout promise, as conceptually outlined in the documentation of $http, and register it in one of your configuration blocks. This means all $http calls are affected by the $timeout delay. Something along the lines of:
$httpProvider.interceptors.push(function($timeout) {
return {
"response": function (response) {
return $timeout(function() {
return response;
}, 2500);
}
};
});
As a bonus to your "to simulate a bad connection?", you could reject or do absolutely nothing randomly, too. Heh heh heh.
The new chrome device emulator has a network throttling function:
To get there: In Google Chrome, press F12 to open the Developer Tools. Then, on the top left corner, click the "Toggle device mode" icon (left to the "Elements" menu).
Developing more on the answer of #stevuu
responseInterceptors seems to be depreceted (as of 1.2.20) I have modified the code to work on the interceptors mechanism:
$httpProvider.interceptors.push(function($q, $timeout) {
return {
'response': function(response) {
var defer = $q.defer();
$timeout(function() {
defer.resolve(response);
}, 2300);
return defer.promise;
}
};
});
You could use the $q service for defer().promise pattern:
function someFunction(MOCK_ajaxDelay) {
var deferred = $q.defer();
$http.post('/ajax/Products.asmx/GetSpecialProducs', data).success(function(response) {
$timeout(function() {deferred.resolve({ success: true, response: response })}, MOCK_ajaxDelay);
}).error(function() {
$timeout(function() {deferred.resolve({ success: true, response: response } }, MOCK_ajaxDelay);
});
return deferred.promise;
}
someService.someFunction(500).then(function(data) {
if (data.success) {
$scope.items = data.response.d;
}
});
But if you are really mock testing, the better solution is to look into ngMock: http://docs.angularjs.org/api/ngMock.$httpBackend
While #stevuu's answer is correct, the syntax has changed in the newer AngularJS versions since then. The updated syntax is:
$httpProvider.interceptors.push(["$q", "$timeout", function ($q, $timeout) {
function slower(response) {
var deferred = $q.defer();
$timeout(function() {
deferred.resolve(response);
}, 2000);
return deferred.promise;
}
return {
'response': slower
};
}]);
You can achieve this using the promise api combined with a $timeout. The $http.post function returns a promise from which you can call .success and .error (these are http specific methods). This promise is resolved when the http request is complete. If you build your own promise then you can tell it to delay 2 seconds and then resolve when the http request is complete:
module.factory('productService', function ($http, $q, $timeout) {
return {
getSpecialProducts: function (data) {
var defer = $q.defer();
$http.post('/ajax/Products.asmx/GetSpecialProducs', data).success(
function(data) {
// successful http request, resolve after two seconds
$timeout(function() {
defer.resolve(data);
}, 2000)
}).error(function() {
defer.reject("Http Error");
})
return defer.promise;
}
}
});
But note - you will have to use promise.then(successCallback, errorCallback) functionality - that is, you'll lose the ability to access http headers, status & config from your controllers/directives unless you explicitly supply them to the object passed to defer.resolve({})
Links:
Defer/Promise Api
Http/Promise Api
Resolve egghead video
In response to the testing aspect of your question, Fiddler has a really useful function that helps when you need to simulate delays:
Click on the AutoResponders tab in Fiddler.
Add a rule with a regex that matches the URL of the request you want to delay.
Set the "respond with" to "*delay:1000" where the number is the delay in milliseconds.
The AutoResponder functionality in Fiddler is extremely useful for testing JS that involves a lot of http requests. You can set it to respond with particular http error codes, block responses, etc.
If you are using a service that returns a promise, then inside you should put a return before the $timeout as well because that returns just another promise.
return dataService.loadSavedItem({
save_id: item.save_id,
context: item.context
}).then(function (data) {
// timeout returns a promise
return $timeout(function () {
return data;
},2000);
});
Hope it helps someone!

AngularJS Resource error callback being called with a promise

I've been writing a service in AngularJS to save some data and, if it fails, alert the user. However, after I create my resource and call $save:
myResource.$save(function(success) {
console.log(success);
}, function(error) {
console.log(error);
});
I expect the error callback's argument to be an object with data, status, headers, etc., but all I get is an object with a "then" function. I tried to mock it up in JSFiddle:
http://jsfiddle.net/RichardBender/KeS7r/1/
However, this example works as I originally expected. I yanked this JSFiddle example and put it in my project and it has the same problem I originally described, despite that as far as I can tell everything else is equal. Does anyone have any idea why this might be? My project was created with Yeoman/Bower/Grunt but I can't see why those things would make a difference here.
Thanks,
Richard
I solved the problem. The error was in my HTTP interceptor, where upon an error code, I was accidentally returning $q.reject(promise) rather than $q.reject(response).
The bugged version:
.factory('httpInterceptor', function($q) {
return function(promise) {
return promise.then(
// On success, just forward the response along.
function(response) {
return response;
},
function(response) {
// ... where I process the error
return $q.reject(promise);
}
);
};
The fixed version:
.factory('httpInterceptor', function($q) {
return function(promise) {
return promise.then(
// On success, just forward the response along.
function(response) {
return response;
},
function(response) {
// ... where I process the error
return $q.reject(response);
}
);
};
-Richard

AngularJS Handle different error codes in different places

I have a service that contains a generic call to $http that all ajax calls uses. Here I have "centralized" error handling where the status codes results in redirects.
"FieldsSync" service:
return $http({
method: 'POST',
url: url,
data: $.extend(defaultPostData, postData)
}).error(function(data, status) {
switch (status) {
case 401: // "Unauthorized". Not logged in.
redirect.toLoginPage();
break;
case 403: // "Forbidden". Role has changed.
redirect.toLogoutPage();
break;
}
});
When calling one of the service functions from the controller I always return the deferred object to be able to hook up more error callbacks to be able to handle errors that should result in some kind of feedback for the user.
Controller:
fieldsSync.createField(newField).success(function(data) {
...
}).error(function(data, status) {
switch (status) { // <--- DO NOT WANT
case 401:
case 403:
return; // These errors are handled (=redirects) in the generic error callback and we don't want to show do anything while it redirects.
}
... // "Individual" error handling. =Displaying messages and stuff
});
But because I don't want error messages popping up before the redirect occurs I have to exit the error callback if the status codes already have been handled.
The question is:
How do i get rid of the switch cases in my controllers?
Is it possible to exit the chain of error callbacks when a specific error code has been handled? Or is it possible to work around this in a less ugly way? :)
This is a reoccurring problem for me, and my mind seems stuck.
I have checked the docs and cant find any pretty solution for this in either $http or $q.
I propose you to use a responseInterceptor to handle those cases:
http://docs.angularjs.org/api/ng.$http
I wrote the following, when I had the same "problem" as you. Its only a first draft, but you can improve it to your needs
InternalServerErrorResponseInterceptor = function($q, InternalServerErrorService) {
return function(promise) {
return promise.then(function(response) {
return response;
}, function(response) {
if (response.status > 499) {
InternalServerErrorService.handleError();
return $q.reject(response);
}
return $q.reject(response);
});
};
};
module.factory('InternalServerErrorResponseInterceptor', ['$q', 'InternalServerErrorService', InternalServerErrorResponseInterceptor]);
module.config(['$httpProvider', function($httpProvider) {
$httpProvider.responseInterceptors.push('InternalServerErrorResponseInterceptor');
}]);
Now your specific InternalServerErrorService can handle it the way you want it to be handled :)
regards
One solution I come up with is to redo the sync functions (puh!) so that they DONT'T return the deferred object. The functions inside the service could take the callbacks as arguments. I could then check if the error already has been handled by the generic error handling before it is called.
Pseudocode:
Service:
fieldsSync.createField(newField, successCallback, failCallback) {
...
genericErrorHandlerResultingInRedirects()
if (not 401 || 403) {
failCallback()
}
};
Controller:
fieldsSync.createField({}, function successCallback(){}, function failCallback(){
/* Handle errors that should give the user feedback */
});
Other solutions are most welcome.

Resources