Handling Errors From Services in Controllers - angularjs

In my AngularJS application I am removing all $http.get() calls from my controllers and putting them into a custom service called $myService which will make the http requests and return the promise object back to the controller that calls the service. The reason for doing this is to remove copy and paste code from my controllers, all of which are getting http resources in the same way and all of which have error handling logic which also is copied and pasted.
The design problem I'm having is I want the error handling logic to be defined in one place and to be usable by any controller. Controllers should be able to request string resources from the service and assign them to the scope like $scope.ButtonText = $myService.resources.ButtonText; and that's ideal because it's just one line of code with no logic. The problem is that if the error handling logic is in the service then I can't (or shouldn't at least) modify the scope from inside the service. What I want is if the error handling logic determines something has gone wrong then it should return an empty string to the controller and set $scope.error to an appropirate error message.
Since I don't want scope manipulation in the service my first and only thought is that I could have the controllers deal with the errors. For instance the call to $myService.resources.ButtonText could return null if there is a problem with the http request and then the controller can assume an error whenever it gets null back. The problem with this is I would have to copy and paste this error handling logic into every controller, undoing my refactoring efforts!
How can I centralize error handling logic in this situation?

So basically anytime there is an http error, rather than a behavior, like forwarding to an error page, you want the particular error available to your controller without having to write a bunch of .success().error() boilerplate in every method.
On thing you might try is broadcasting the error from your service, then you can listen wherever you want. It might be handy to have a service that listens or maybe a controller that deals with providing error feedback on the page.
Maybe a variation of this basic idea:
services.factory('myService', ['$q', '$http', '$rootScope', function( $q, $http, $rootScope){
function getSomething(someID){
$http.get(url).success(function (data) {
deferred.resolve(data);
}).error(function(error) {
$rootScope.$broadcast('http-error', { foo: 'bar' });
deferred.reject(null);
});
Then somewhere, either a controller or a service you inject into a controller you can catch the errors and have access to the information you want:
$rootScope.$on('http-error', function handler(obj) {
//handle error
console.log("error:", obj)
});
If you write this into a service, then you could save the error in the service than it would be available to any controller that injected the service i.e. myErrorService.httpError

to solve this I would use a http interceptor and a http wrapper for the calls.
I'm actually in the process of refactoring this in my app.
Here is the interceptor, to handle http errors and redirect to login page if necessary:
app.factory('errorsInterceptor', ['$rootScope', '$q', '$window', '$location', 'localStorageService', 'Logger', function ($rootScope, $q, $window, $location, localStorageService, Logger) {
return {
response: function (response) {
if (response.status === 401) {
$location.path('/pages/signin');
}
return response || $q.when(response);
},
responseError: function (rejection) {
if (rejection.status === 401) {
$window.sessionStorage.redirectUrl = $location.path();
$location.path('/pages/signin');
}
return $q.reject(rejection);
}
};
}]);
And here is how you can plug it in:
app.config(['$httpProvider', function ($httpProvider) {
$httpProvider.interceptors.push('errorsInterceptor');
}]);
Here is my http wrapper, from which I'll make all the api calls:
(function () {
'use strict';
var serviceId = 'HttpService';
angular.module('app').factory(serviceId, ['$http', '$q', HttpService]);
function HttpService($http, $q) {
var service = {
get: getData,
};
return service;
function getData(url) {
var deferred = $q.defer();
$http.get(url).success(function (data) {
deferred.resolve(data);
}).error(function (error) {
// put your global error handling here, in this case it just returns null as you said
deferred.reject(null);
// you could also return the error message:
// deferred.reject(error.message);
});
return deferred.promise;
}
}
})();
Now an example of how you can use that in your app's controllers:
(function () {
'use strict';
var controllerId = 'ExampleCtrl';
angular.module('app').controller(controllerId,
['$scope', 'HttpService', ExampleCtrl]);
function ExampleCtrl($scope, HttpService) {
HttpService.get('my-endpoint-url').then(function(returnedData){
$scope.title = returnedData;
});
}
})();
With this, "title" gets the value returned from the endpoint in case of success or NULL in case of an error.
To understand this better, I recommend to study promises, here's an useful link:
http://liamkaufman.com/blog/2013/09/09/using-angularjs-promises/
Hope that helps.

Instead of returning a string from your service, return an object with a value and an error property. If there's an error the data will be null and the error will contain the error information which your controller can use however it wants.
For instance, your service can return on error:
{"value":null, "error":"404 Error"}
or on success:
{"value":"My awesome string", "error":null}
Then in your controller you can do something like this:
$scope.foo = val.value || val.error
or something a little nicer that the offers different behavior based on the return.

you need to write an http interceptor....
just an example below
module.factory('timestampMarker', [function() {
var timestampMarker = {
responseError: function(response) {
console.log(response);
toastr.error(response.Message)
return response;
}
};
return timestampMarker;
}]);
module.config(['$httpProvider', function($httpProvider) {
$httpProvider.interceptors.push('timestampMarker');
}]);
you can read this great article on the http interceptors
another article on http interceptor
EDIT (as per comment)
the requirement is to have global error handling mechanism which does return null on any sort of failure in any http call across the application...
httpInterceptor is the place where you can do such global handling of http calls...
e.g. if you get 401/unauthenticated response from server you can to redirect user to login page, or
if you get an error, you can log that error in console or display an alert
so this error handling is implemented for all the http calls, and it keeps you individual http class DRY.
you can use this httpInterceptor to return null for any kind of error in http call response, so then as you said, you dont have to write set to null statement in each http response.
though, my suggestion would be against returning null on all sort of error and let the individual http call take the decision of how to handle error. yes, implement global error handling, but that should only be for errors like 401 error.

Related

Angular JS: Uncaught SyntaxError: Unexpected token

I have one error in $http.json line.
Here is my code:
var myApp = angular.module('myApp', ["ionic"]);
myApp.service("Pressed", ["$http", "$log", Pressed]);
myApp.controller("AppCtrl", ["$scope", "Pressed", "$log", AppCtrl]);
function AppCtrl($scope, Pressed, $log){
$scope.refresh = function(){
Pressed.getBlogs();
}
}
function Pressed($http, $log) {
this.getBlogs = function() {
$http.jsonp('http://oasisgroups.com/oApp/product.php?callback=JSON_CALLBACK()')
.success(function(result){
$log.info(JSON.stringify(result.product));
});
};
}
When I click on refresh button, an error is displayed in the console:
You can also here find the respective service.
I believe the main issue is that the service doesn't seem to support a jsonp call. No matter how I call the service you provided it only responds with standard JSON results and not with the JSON wrapped in the callback function. Your screen shot of Chrome even shows the raw JSON, not JSONP response from the service. If a service doesn't support JSONP you can't force it to, that is something each service does on a case by case basis depending on how it is written. So the root cause of your error is that AngularJS is expecting the callback function to be part of the response, it cannot find it, and you get the error you are seeing.
I have constructed a jasmine test for your code and it passes. That is the best I can do to confirm that your code is correct and the issue is outside of your Angular code.
Unless the web service actually responds with the expected callback function wrapping the JSON, you need to switch to a standard $http.get() and deal with any potential XSS issues that you might encounter in a different way.
You can see a working JSONP example with this url. You will note that it starts with "getdata" and then wraps the JSON content inside that function's (). Your service is not doing that with the callback query string attribute.
var myApp = angular.module('myApp', []);
myApp.controller("AppCtrl", ["$scope", "Pressed", "$log", function ($scope, Pressed, $log) {
$scope.refresh = function () {
Pressed.getBlogs($scope);
}
}]);
myApp.service('Pressed', ['$http', '$log', function ($http, $log) {
var pressed = {};
pressed.getBlogs = function ($scope) {
$http.jsonp('http://oasisgroups.com/oApp/product.php?callback=JSON_CALLBACK')
.success(function (data,status,headers,config) {
$log.info(JSON.stringify(data));
$scope.products = data.product;
console.log('Found ' + data.product.length + ' products');
})
.error(function () {
console.log("Error during http get request.");
});
};
return pressed;
}]);
Then the test would look something like this:
describe('bad_jsonp', function () {
var service, scope;
beforeEach(module('myApp'));
beforeEach(angular.mock.inject(function ($rootScope) {
scope = $rootScope.$new();
}));
beforeEach(inject(function($httpBackend, _Pressed_) {
backend = $httpBackend;
service = _Pressed_;
}));
it('test that service response contains the attribute product', function () {
backend.expect("JSONP","http://oasisgroups.com/oApp/product.php?callback=JSON_CALLBACK").
respond(200,
{"success":1,"msg":"success","product":[{"image":"http:\/\/oasisgroups.com\/images\/oacgallery\/160112WP_20160112_17_57_49_Pro__1452604019_113.193.193.146.jpg","title":"Shreenath Ji"},{"image":"http:\/\/oasisgroups.com\/images\/oacgallery\/1601124e199090-c030-4f01-be11-c5140cf20273__1452603831_113.193.193.146.jpg","title":"Acrylic Jali"},{"image":"http:\/\/oasisgroups.com\/images\/oacgallery\/1601128a718e95-7df0-4189-876e-204b715cf90d__1452603868_113.193.193.146.jpg","title":"Acrylic Jali"},{"image":"http:\/\/oasisgroups.com\/images\/oacgallery\/16011229b095c9-b897-4942-831f-92073f527374__1452603895_113.193.193.146.jpg","title":"Wooden Decorative"},{"image":"http:\/\/oasisgroups.com\/images\/oacgallery\/16011255ce3155-3956-4cfb-8dd5-39021713d350__1452603914_113.193.193.146.jpg","title":"Acrylic Jali Oranage"},{"image":"http:\/\/oasisgroups.com\/images\/oacgallery\/160112WP_20160112_17_33_11_Pro__1452603994_113.193.193.146.jpg","title":"Acrylic Jali Green"},{"image":"http:\/\/oasisgroups.com\/images\/oacgallery\/160112607c733c-8dd5-442c-a584-6179339abb0e__1452603974_113.193.193.146.jpg","title":"Acrylic Jali White"},{"image":"http:\/\/oasisgroups.com\/images\/oacgallery\/160112300cca44-e783-48f7-b035-59ef0529ad53__1452603956_113.193.193.146.jpg","title":"Wooden Decorative"},{"image":"http:\/\/oasisgroups.com\/images\/oacgallery\/16011279e7c001-6663-4dfe-91ce-70cc87e6ca2d__1452603940_113.193.193.146.jpg","title":"Wooden Decorative"},{"image":"http:\/\/oasisgroups.com\/images\/oacgallery\/160112WP_20160112_17_58_35_Pro__1452604069_113.193.193.146.jpg","title":"Corian Design "},{"image":"http:\/\/oasisgroups.com\/images\/oacgallery\/160112WP_20160112_17_59_14_Pro__1452604098_113.193.193.146.jpg","title":"Corian Design "},{"image":"http:\/\/oasisgroups.com\/images\/oacgallery\/160112WP_20160112_18_00_34_Pro__1452604138_113.193.193.146.jpg","title":"AalaBuster"},{"image":"http:\/\/oasisgroups.com\/images\/oacgallery\/160112WP_20160112_18_01_20_Pro__1452604320_113.193.193.146.jpg","title":"AalaBuster"},{"image":"http:\/\/oasisgroups.com\/images\/oacgallery\/160112WP_20160112_18_02_08_Pro__1452604343_113.193.193.146.jpg","title":"Corian wash basin"},{"image":"http:\/\/oasisgroups.com\/images\/oacgallery\/160112WP_20160112_18_02_25_Pro__1452604370_113.193.193.146.jpg","title":"3d Corian Design"},{"image":"http:\/\/oasisgroups.com\/images\/oacgallery\/160112WP_20160112_18_02_43_Pro__1452604393_113.193.193.146.jpg","title":"3d Corian Design"},{"image":"http:\/\/oasisgroups.com\/images\/oacgallery\/160112WP_20160112_18_03_13_Pro__1452604424_113.193.193.146.jpg","title":"3d Wooden Decorative"}]}
);
expect(service).toBeDefined();
service.getBlogs(scope);
backend.flush();
console.log(scope.products);
var products = scope.products;
expect(products.length).toBe(17);
expect(products[0].title).toBe("Shreenath Ji");
});
});
The test doesn't include the actual callback in the response content because the mocking framework handles that wrapping and unwrapping for you just like AngularJS does in the first place, so it isn't an exact test but it is as close as I can get with what we have.

How can I create a service that returns the value promise

I want to create a service that returns a json
Or by request to to the server, or by checking if it exists already in: Window.content
But I don't want to get a promise from my Controller !
I want to get the json ready !
I have tried several times in several ways
I tried to use with then method to do the test in my Service
but I still get a promise
( Whether with $http only, and whether with $q )
I could not get the value without getting promise from my Controller
My Service :
app.service('getContent',['$http', function( $http ){
return function(url){ // Getting utl
if(window.content){ // if it's the first loading, then there is a content here
var temp = window.content;
window.content = undefined;
return temp;
}
return $http.get(url);
};
}]);
My Controller:
.state('pages', {
url: '/:page',
templateProvider:['$templateRequest',
function($templateRequest){
return $templateRequest(BASE_URL + 'assets/angularTemplates/pages.html');
}],
controller: function($scope, $stateParams, getContent){
// Here I want to to get a json ready :
$scope.contentPage = getContent(BASE_URL + $stateParams.page + '?angular=pageName');
}
});
If the data exists, just resolve it in a promise.
While this process is still asynchronous it won't require a network call and returns quickly.
app.service('getContent',['$http', '$q', function( $http, $q ){
return function(url){
// create a deferred
var deferred = $q.defer();
if(window.content){ // if it's the first loading, then there is a content here
var temp = window.content;
window.content = undefined;
deferred.resolve(temp); // resolve the data
return deferred.promise; // return a promise
}
// if not, make a network call
return $http.get(url);
};
}]);
Just to reiterate, this asynchronous, but it won't require a network call.
This is not possible. If the code responsible to calculate or retrieve the value relies on a promise, you will not be able to return the value extracted from the promise by your function.
Explanation: This can easily be seen from the control flow. A promise is evaluated asynchronously. It may take several seconds to retrieve json from a server, but the caller of your function should not wait so long because your whole runtime environment would block. This is why you use promises in the first place. Promises are just a nice way to organize callbacks. So when your promise returns, the event that caused the function call will have already terminated. In fact it must have, otherwise your promise could not be evaluated.
You're thinking about this wrong. A service always returns a promise, because there is no synchronous way of getting JSON from an API:
app.factory('myService', ['$http', function($http) {
return $http('http://my_api.com/json', function(resp) {
return resp.data;
});
}]);
You would then call this within your controller like so:
app.controller('myController', ['$scope', 'myService', function($scope, myService) {
myService.then(function(data) {
$scope.contentPage = data; // here is your JSON
}, function(error) {
// Handle errors
});
}]);
Your service is returning a promise as it's written at the moment. A promise is always a promise, because you don't really know when it will be finished. However with Angular's 2 way data binding this isn't an issue. See my edits bellow as well as the example on $HTTP in the docs
In your controller
controller: function($scope, $stateParams, getContent){
getContent(BASE_URL + $stateParams.page + '?angular=pageName')
.then(aSuccessFn, aFailedFn);
function aSuccessFn(response) {
// work with data object, if the need to be accessed in your template, set you scope in the aSuccessFn.
$scope.contentPage = response.data;
}
function aFailedFn(data) {
// foo bar error handling.
}
}

AngularJS: make data received with $http inside factory to be accessible inside controller

I have a factory called "Server" which contains my methods for interaction with the server (get/put/post/delete..). I managed to login and get all data successfully when I had all my code in my controller. Now that I want to separate this code and restructure it a little bit I ran into problems. I can still login and I also get data - but data is just printed; I'm not sure how to access the data in controller? I saw some ".then" instead of ".success" used here and there across the web, but I don't know how exactly.
This is my factory: (included in services.js)
app.factory('Server', ['$http', function($http) {
return {
// this works as it should, login works correctly
login: function(email,pass) {
return $http.get('mywebapiurl/server.php?email='+email+'&password='+pass').success(function(data) {
console.log("\nLOGIN RESPONSE: "+JSON.stringify(data));
if(data.Status !== "OK")
// login fail
console.log("Login FAIL...");
else
// success
console.log("Login OK...");
});
},
// intentional blank data parameter below (server configured this way for testing purposes)
getAllData: function() {
return $http.get('mywebapiurl/server.php?data=').success(function(data) {
console.log("\nDATA FROM SERVER: \n"+data); // here correct data in JSON string format are printed
});
},
};
}]);
This is my controller:
app.controller("MainController", ['$scope', 'Server', function($scope, Server){
Server.login(); // this logins correctly
$scope.data = Server.getAllData(); // here I want to get data returned by the server, now I get http object with all the methods etc etc.
…. continues …
How do I get data that was retrieved with $http within a factory to be accessible in controller? I only have one controller.
Thanks for any help, I'm sure there must be an easy way of doing this. Or am I perhaps taking a wrong way working this out?
EDIT: I also need to be able to call factory functions from views with ng-click for instance. Now I can do this like this:
// this is a method in controller
$scope.updateContacts = function(){
$http.get('mywebapiURL/server.php?mycontacts=').success(function(data) {
$scope.contacts = data;
});
};
and make a call in a view with ng-click="updateContacts()". See how $scope.contacts gets new data in the above function. How am I supposed to do this with .then method?(assigning returned data to variable)
My question asked straight-forwardly:
Lets say I need parts of controller code separated from it (so it doesn't get all messy), like some functions that are available throughout all $scope. What is the best way to accomplish this in AngularJS? Maybe it's not services as I thought …
The trick is to use a promise in your service to proxy the results.
The $http service returns a promise that you can resolve using then with a list or success and error to handle those conditions respectively.
This block of code shows handling the result of the call:
var deferred = $q.defer();
$http.get(productsEndpoint).success(function(result) {
deferred.resolve(result);
}).error(function(result) { deferred.reject(result); });
return deferred.promise;
The code uses the Angular $q service to create a promise. When the $http call is resolved then the promise is used to return information to your controller. The controller handles it like this:
app.controller("myController", ["$scope", "myService", function($scope, myService) {
$scope.data = { status: "Not Loaded." };
myService.getData().then(function(data) { $scope.data = data; });
}]);
(Another function can be passed to then if you want to explicitly handle the rejection).
That closes the loop: a service that uses a promise to return the data, and a controller that calls the service and chains the promise for the result. I have a full fiddle online here: http://jsfiddle.net/HhFwL/
You can change the end point, right now it just points to a generic OData end point to fetch some products data.
More on $http: http://docs.angularjs.org/api/ng.%24http
More on $q: http://docs.angularjs.org/api/ng.%24q
$http.get retuns a HttpPromise Object
Server.getAllData().then(function(results){
$scope.data = results;
})

AngularJS - Should we wrap $http methods in a service

My question is that given the power of interceptors, does it make sense to wrap $http in a service so that all my other code just calls that wrapper. Now basic tasks like header/exception handling can easily be done by interceptors. So though I cant think of a valid usecase now, but lets say just to shield any future api changes etc to $http? Or maybe later migrate to $resource?
Also please note that here I am talking of a basic wrapper service around $http’s methods, not a client service like DataService with methods sendData, receiveData that wraps $http calls.
Please find below sample code -
angular.module(‘myapp’).factory(‘myhttpwrapper’, ['$http', function (http) {
return {
myGet: function (getUrl) {
return http.get(getUrl);
},
myPost: function (postUrl, data) {
return http.post(postUrl, data);
},
// other $http wrappers
};
}]);
Now all other code will use myhttpwrapper’s myGet, myPost methods instead of $http’s get, post methods. Hope it makes sense!
[EDIT] What use-cases we'll definitely have is to intercept requests for adding headers, logging, and responses for logging, exception handling, etc. But I am sure these can be handled by interceptors. Later moving from $http to $resource is not known at this point.
Thanks.
For the specific case you describe, I'd advise against wrapping $http. There is no real gain in doing it.
A case where this could come into play would be where you'd want a more 'speaking' API. Lets say you have User(s) in a system, and Address(es). You described it as a data based service DataService:
var app = angular.module("users", []);
app.service("User", ['$http', '$q', function(http, q) {
return {
getAddress: function(user) {
var address = q.defer();
http.get("/user/" + user.id + "/address").then(function(data) {
address.resolve(data);
}, function(err) {
address.reject(err);
});
return address.promise;
},
getUser: function() {
var user = = q.defer();
http.get("/user/address").then(function(data) {
user.resolve(data);
}, function(err) {
user.reject(err);
});
return user.promise;
}
}
}]);
This allows you to use parameters for your call. Whenever you'd have to change routes, you would have only one place to change them (this would be really awful if, say, you had a dozen controllers making $http requests).
You can also make use of $resource here if you which (and you actually have compatible resources). The decision making factor(s) here should be readability, reusability and ease of change later.
So, if you only ever have on place where you make these calls and the routes never change, go ahead and use the $http directly, without a wrapper. This I'd consider unlikely in a growing application.
I needed my 'get' generalized so did it like this:
app.factory('myHttp', ['$http', '$q', function($http, $q) {
return {
get: function(url) {
var deferred = $q.defer();
$http.get(url).then(
function(response) {
//console.log("response good");
deferred.resolve(response)
},
function(response) {
//console.log("response bad");
deferred.reject(response)
})
return deferred.promise;
}
};
}]);
You can now chain an additional "then" to the result of the get. You can have core logic for handling error or success (my use case being to log out the user if credentials expired) but still allow for additional context specific logic for the get response (like setting $scope etc...).

Angularjs Post-Receive Hook or Similar?

Is there a way to call a function every time after a response is returned from a server without explicitly calling it after in the callback?
The main purpose is that I do have a generic error handler service that I call in every request's callback and I want to specify it somewhere and it shall be called automatically.
I gave Gloopy a +1 on solution, however, that other post he references does DOM manipulation in the function defined in the config and the interceptor. Instead, I moved the logic for starting spinner into the top of the intercepter and I use a variable in the $rootScope to control the hide/show of the spinner. It seems to work pretty well and I believe is much more testable.
<img ng-show="polling" src="images/ajax-loader.gif">
angular.module('myApp.services', ['ngResource']).
.config(function ($httpProvider) {
$httpProvider.responseInterceptors.push('myHttpInterceptor');
var spinnerFunction = function (data, headersGetter) {
return data;
};
$httpProvider.defaults.transformRequest.push(spinnerFunction);
})
//register the interceptor as a service, intercepts ALL angular ajax http calls
.factory('myHttpInterceptor', function ($q, $window, $rootScope) {
return function (promise) {
$rootScope.polling = true;
return promise.then(function (response) {
$rootScope.polling = false;
return response;
}, function (response) {
$rootScope.polling = false;
$rootScope.network_error = true;
return $q.reject(response);
});
};
})
// other code left out
If you mean for requests using $http or a $resource you can add generic error handling to responses by adding code to the $httpProvider.responseInterceptors. See more in this post.
Although it is about starting/stopping spinners using this fiddle you can add your code in the 'stop spinner' section with // do something on error. Thanks to zdam from the groups!

Resources