Angularjs Post-Receive Hook or Similar? - angularjs

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!

Related

Using the service data in the controller

I have placed the service and the controller in the same js file. So Im trying to fetch the data from the service and use it in my html. In my code Im able to generate the data from the service but not able to assign it to a $scope in the controller and use it in the html. So how do I get the data and assign it to the $scope so that I can use it in my html.
var app = angular.module("app",[]);
app.factory('factoryServices',function($http){
var newObject = {};
var _getChart= function(){
$http.get("http://citibikenyc.com/stations/json")
.success(function(data, status){
if(data) {
return data;
}
}).error(function(data,status){
return error;
});
}
newObject.getChart = _getChart;
return newObject;
});
app.controller("chartController",function($scope,$http,factoryServices){
factoryServices.getChart($scope.chartServicesCompleted);
$scope.chartServicesCompleted = function(data){
$scope.serviceResponse = data;
}
})
If you rewrite your code like this, it should work as expected:
var app = angular.module("app",[]);
app.factory('factoryServices',function($http){
var newObject = {};
var _getChart= function(){
return $http.get("http://citibikenyc.com/stations/json")
.then(function(response){
if(response.data) {
return response.data;
}
}, function(response){
console.error("getChart failed with ",response);
});
}
newObject.getChart = _getChart;
return newObject;
});
and your controller
app.controller("chartController",function($scope,$http,factoryServices){
factoryServices.getChart().then(chartServicesCompleted);
function chartServicesCompleted(data){
$scope.serviceResponse = data;
}
})
The reason your initial code doesn't work, is because your getChart doesn't actually take an argument. So passing your callback like this: getChart($scope.chartServicesCompleted) doesn't do anything. In the rewritten code, I've made it so the getChart function returns the promise created by $http.get(..) which then allows you to use .then([callback]) in your controller.
you are passing a callback function but not handling inside the service method.
do change as
var _getChart= function(callback){
$http.get("http://citibikenyc.com/stations/json")
.success(function(data, status){
callback(data);
}).error(function(data,status){
callback(data);
});
}
now factoryServices.getChart($scope.chartServicesCompleted); will work
you can make more generic by handling success and error callback separately.
or one more way is to implement the success and error logic inside your controller.
but do not forget to check the function type i.e
if(typeof callback == 'function'){
callback(data);
}
Edit: as per advanced you call implement promises.
if you are using angular version 1.6, the success and error methods have been depreciated.
secondly you can do inside service return the http object
var _getChart= function(){
return $http.get("http://citibikenyc.com/stations/json");
}
and then handle the promise in the controller like
factoryServices.getChart().then(successMethod, error method);
Let the service return the promise to the controller. ex:
var _getChart= function(){
return $http.get("http://citibikenyc.com/stations/json");
}
and in the controller handle the promise. Use 'then' instead
factoryServices.getChart().then(function(response){var theDate = response.data},function(error){});
you can declare methods instead in the controller for handling the success and error
factoryServices.getChart).then(onSuccess,onError);
Don use the .success method and .error method. They dont't behave as other promises. So get used to 'then'
You really don't need to handle errors in the service method.I use angular interceptors in most of the cases. Check em out. But sometimes you need to handle the error in the controller. So its good to get the callback in the controller

how to prevent duplicated $http requests in AngularJS app?

I need to prevent sending the same request repeatedly to API before the previous request will give the response. I have found out some solutions. But, I don't want to disable the button while waiting for response because I have more API calls in my app.
I really need to do something in my $provider .config() .I found a way here(http://blog.codebrag.com/post/57412530001/preventing-duplicated-requests-in-angularjs).
But I need more clarification code. Any kind of reference about this is welcome.
Lets say you have $http in your controller.js file.
Many request to server
$http.get('/link/to/file.php');
Just one request to server, no matter how many times you will call this method:
$http.get('/link/to/file.php', {cache: true});
Example:
(function() {
'use strict';
angular
.module('yourModuleName')
.controller('DashboardCtrl', DashboardCtrl);
DashboardCtrl.$inject = ['$scope'];
function DashboardCtrl($scope) {
$scope.get = function () {
$http.get('/link/to/file.php', {cache: true}).then(function(response) {
// it will do GET request just once
// later you will get the same response from cacheFactory
})
}
}
}());
I would like to complement #VikasChauhan answer, however I do not have enough reputation to comment on his answer.
His code works great for me, except the part where he returns null. That causes Angular to throw a bunch of errors all over.
Instead of null, I simply reject the request:
return $q.reject(request);
Here's my function:
$httpProvider.interceptors.push(['$injector', '$q', function interceptors($injector, $q) {
return {
// preventing duplicate requests
request: function request(config) {
var $http = $injector.get('$http');
var _config = angular.copy(config);
delete _config.headers;
function isConfigEqual(pendingRequestConfig) {
var _pendingRequestConfig = angular.copy(pendingRequestConfig);
delete _pendingRequestConfig.headers;
return angular.equals(_config, _pendingRequestConfig);
}
if ($http.pendingRequests.some(isConfigEqual)) {
return $q.reject(request);
}
return config;
}
};
}
]);
Hope this helps other people.
You can create a function to cancel the first http request, when calling the another one on the button click.
Here is a reference that uses $q.defer() function that helped me on a similar issue:
http://odetocode.com/blogs/scott/archive/2014/04/24/canceling-http-requests-in-angularjs.aspx
In my project, i was facing this problem. I found a very useful working solution here
And implemented it inside the config:
function config($routeProvider, $httpProvider) {
$httpProvider.interceptors.push(['$injector', function interceptors($injector) {
// Manually injecting dependencies to avoid circular dependency problem
return {
// preventing duplicate requests
'request': function request(config) {
var $http = $injector.get('$http'),
copiedConfig = angular.copy(config);
delete copiedConfig.headers;
function configsAreEqual(pendingRequestConfig) {
var copiedPendingRequestConfig = angular.copy(pendingRequestConfig);
delete copiedPendingRequestConfig.headers;
return angular.equals(copiedConfig, copiedPendingRequestConfig);
}
if ($http.pendingRequests.some(configsAreEqual)) {
debugger;
return null;
}
return config;
}
}
}
]);
}

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.
}
}

Handling Errors From Services in Controllers

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.

How to wait till the response comes from the $http request, in angularjs?

I am using some data which is from a RESTful service in multiple pages.
So I am using angular factories for that. So, I required to get the data once from the server, and everytime I am getting the data with that defined service. Just like a global variables. Here is the sample:
var myApp = angular.module('myservices', []);
myApp.factory('myService', function($http) {
$http({method:"GET", url:"/my/url"}).success(function(result){
return result;
});
});
In my controller I am using this service as:
function myFunction($scope, myService) {
$scope.data = myService;
console.log("data.name"+$scope.data.name);
}
Its working fine for me as per my requirements.
But the problem here is, when I reloaded in my webpage the service will gets called again and requests for server. If in between some other function executes which is dependent on the "defined service", It's giving the error like "something" is undefined. So I want to wait in my script till the service is loaded. How can I do that? Is there anyway do that in angularjs?
You should use promises for async operations where you don't know when it will be completed. A promise "represents an operation that hasn't completed yet, but is expected in the future." (https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise)
An example implementation would be like:
myApp.factory('myService', function($http) {
var getData = function() {
// Angular $http() and then() both return promises themselves
return $http({method:"GET", url:"/my/url"}).then(function(result){
// What we return here is the data that will be accessible
// to us after the promise resolves
return result.data;
});
};
return { getData: getData };
});
function myFunction($scope, myService) {
var myDataPromise = myService.getData();
myDataPromise.then(function(result) {
// this is only run after getData() resolves
$scope.data = result;
console.log("data.name"+$scope.data.name);
});
}
Edit: Regarding Sujoys comment that
What do I need to do so that myFuction() call won't return till .then() function finishes execution.
function myFunction($scope, myService) {
var myDataPromise = myService.getData();
myDataPromise.then(function(result) {
$scope.data = result;
console.log("data.name"+$scope.data.name);
});
console.log("This will get printed before data.name inside then. And I don't want that.");
}
Well, let's suppose the call to getData() took 10 seconds to complete. If the function didn't return anything in that time, it would effectively become normal synchronous code and would hang the browser until it completed.
With the promise returning instantly though, the browser is free to continue on with other code in the meantime. Once the promise resolves/fails, the then() call is triggered. So it makes much more sense this way, even if it might make the flow of your code a bit more complex (complexity is a common problem of async/parallel programming in general after all!)
for people new to this you can also use a callback for example:
In your service:
.factory('DataHandler',function ($http){
var GetRandomArtists = function(data, callback){
$http.post(URL, data).success(function (response) {
callback(response);
});
}
})
In your controller:
DataHandler.GetRandomArtists(3, function(response){
$scope.data.random_artists = response;
});
I was having the same problem and none if these worked for me. Here is what did work though...
app.factory('myService', function($http) {
var data = function (value) {
return $http.get(value);
}
return { data: data }
});
and then the function that uses it is...
vm.search = function(value) {
var recieved_data = myService.data(value);
recieved_data.then(
function(fulfillment){
vm.tags = fulfillment.data;
}, function(){
console.log("Server did not send tag data.");
});
};
The service isn't that necessary but I think its a good practise for extensibility. Most of what you will need for one will for any other, especially when using APIs. Anyway I hope this was helpful.
FYI, this is using Angularfire so it may vary a bit for a different service or other use but should solve the same isse $http has. I had this same issue only solution that fit for me the best was to combine all services/factories into a single promise on the scope. On each route/view that needed these services/etc to be loaded I put any functions that require loaded data inside the controller function i.e. myfunct() and the main app.js on run after auth i put
myservice.$loaded().then(function() {$rootScope.myservice = myservice;});
and in the view I just did
ng-if="myservice" ng-init="somevar=myfunct()"
in the first/parent view element/wrapper so the controller can run everything inside
myfunct()
without worrying about async promises/order/queue issues. I hope that helps someone with the same issues I had.

Resources