I have the following interceptor:
var interceptor = ['$http', '$q', function ($http, $q) {
//...
}];
This generates a circular dependency, because $http will depend on this interceptor, and this interceptor will depend on $http.
I want to get the $http service because I intend to refresh some authorization tokens of the user if I get a 401 Unauthorized response.
For this, I have to call my OAuth endpoint and retrieve the new tokens.
How can I inject this service into my interceptor?
I also tried the following alternative:
var interceptor = ['$injector', '$q', function ($injector, $q) {
var $http = $injector.get('$http');
}]
But I'm getting the same error.
Is this possible?
I don't want to use the jQuery library and I want my application to be pure AngularJS, so $.ajax(...) answers are not useful to me.
Even the second snippet causes cdep error because interceptor service will get instantiated and in the constructor you are trying to get $http in the process, which causes cdep error. You would need to get the http service (or any of your service that injects http) later, after interceptor service has been instantiated. You could easily get it from $injector on demand when you need it, example on a reponseError.
var interceptor = ['$injector', '$q',
function($injector, $q) {
return {
responseError: function(rejection) {
//Get it on demand or cache it to another variable once you get it. But you dont really need to do that you could get from the injector itself since it is not expensive as service is a singleton.
var $http = $injector.get('$http');
//Do something with $http
return $q.reject(rejection);
}
}
}
]
try wrapping your injector code with in anonymous function
var interceptor = ['$injector', '$q', function ($injector, $q) {
return function(){
var $http = $injector.get('$http');
}
}];
Related
I have the following code:
$http.get(url).success(function(response,status,header,config) {
$scope.mymodel = response;
}
I want to check the http status and call a function.
I have to do this change on about 100 http.get calls in whole project. So I want to skip this overriding the success function like this.
function success(response,status,header,config) {
if(status!=200){
//my stuff
}
super.success(response,status,header,config);
}
Another possibility is replace $http.get(url).success for $http.get(url).success2 in order to tell my mates: "From now on guys use $http.get(url).success2 is cooler!"
Update
I have already know is a deprecated function, I must use it because is a project requirement.
You should use $httpProvider.interceptors to achieve this.
You can sniff the $http request and response between to do whatever you want and return the $q unscathed.
$httpProvider.interceptors.push(['$q', 'mathScriptLoadError', function($q, mathScriptLoadError) {
return {
requestError: function(rejection){
mathScriptLoadError.anyError(rejection);
return $q.reject(rejection);
},
responseError: function(rejection){
mathScriptLoadError.anyError(rejection);
return $q.reject(rejection);
}
};
}]);
In the above code I get the anyError function of the factory mathScriptLoadError to inspect the rejection and invoke some process based on values. But this doesn't disturb the $http.get at any level.
Deprecation Notice
The $http legacy promise methods success and error have been deprecated.
Use the standard then method instead.
If $httpProvider.useLegacyPromiseExtensions is set to
false then these methods will throw $http/legacy error.
-- AngularJS $http Service API Reference -- Deprecation Notice
Tell your mates: "From now on guys use $http.get(url).then is cooler!"
For other readers:
Response Interceptors
The AngularJS framework provides three places to inject response interceptor functions.
The $http service, see AngularJS $http Service API Reference -- interceptors.
The $resource service, see AngularJS $resource Service API Reference.
Configuring the $httpProvider, see AngularJS $httpProvider API -- interceptors.
XHR responses can be intercepted and modified globally, locally, or by class.
I am really new to AngularJS and after reading several questions and some articles I am a little confused about the correct way to load data and wait till its loaded to display the view.
My controller looks like this
app.controller('ResultsController', ['$scope','$http', '$routeParams', function($scope, $http, $routeParams) {
$scope.poll = {};
$scope.$on('$routeChangeSuccess', function() {
showLoader();
$http.get("rest/visualizacion/" + $routeParams.id)
.success(function(data) {
$scope.poll = data;
hideLoader();
})
.error(function(data) {
// Handle error
});
});
}]);
I have seen there are people who create a service for $http calls, is it necessary? Why is it better?
The appropriate way to do that is to use the resolve property of the route. From the documentation:
resolve - {Object.<string, function>=} - An optional map of dependencies which should be injected into the controller. If any of these dependencies are promises, the router will wait for them all to be resolved or one to be rejected before the controller is instantiated. If all the promises are resolved successfully, the values of the resolved promises are injected and $routeChangeSuccess event is fired. If any of the promises are rejected the $routeChangeError event is fired. The map object is:
key – {string}: a name of a dependency to be injected into the controller.
factory - {string|function}: If string then it is an alias for a service. Otherwise if function, then it is injected and the return value is treated as the dependency. If the result is a promise, it is resolved before its value is injected into the controller. Be aware that ngRoute.$routeParams will still refer to the previous route within these resolve functions. Use $route.current.params to access the new route parameters, instead.
So, if you want poneys to be retrieved from the backend before the router goes to the poney list page, you would have
resolve: {
poneys: function($http) {
return $http.get('/api/poneys').then(function(response) {
return response.data;
)};
}
}
And your controller would be defined as
app.controller('PoneyListCtrl", function($scope, poneys) {
$scope.poneys = poneys;
// ...
});
Of course, you could also put the code making the $http call and returning a list of poneys in a service, and use that service in the resolve.
I'm writing a simple app for displaying (read-only) employee information. I would like to load the info from JSON once only. Not sure what the convention is around this in the angular factory.
I know that one solution is to but the JSON file in a javascript file and load it as a js file (but I would want to keep the file as JSON).
I guess I could also wrap the http call in a promise, and change the return accordingly.
Is there a way of doing this without changing the return? Block on the employee load ?
.factory('Employees', ['$http', function($http) {
var employees = $http.get('res/employees.json').then(function(response){
return response.data; // This is async so won't return right away
});
// This way works (since not async)
// var employees = [
// {
// "id": 232,
// "name": "Bob"
// }];
return {
all: function() {
return employees; // This will return empty before employees is loaded
}
}
}]);
This is a wrong implementation of the promise pattern. Your 'employee' service should return a promise also that gets initialized and then returns the same resolved promise upon subsequent requests. Something like this:
.factory('Employees', ['$q', '$http', function($q, $http) {
var _deferred = $q.defer();
$http.get('res/employees.json')
.success(function(data, status, headers, config) {
// this callback will be called asynchronously
// when the response is available
_deferred.resolve(data);
})
.error(function(data, status, headers, config) {
// called asynchronously if an error occurs
// or server returns response with an error status.
_deferred.reject("Error");
});
};
return {
getEmployees: function(){
return _deferred.promise;
}
}
}]);
.controller('MyController', ['$scope', 'Employees', function($scope, Employees) {
$scope.employees = [];
$scope.employees = Employees.getEmployees();
}]);
$scope.employees will initially be an empty array until the promise is resolved. Also, this code does not have error recovery.
One possible solution that might work for you is to fetch the data and manually bootstrap your application with an appended value or service of the fetched data. There are already built solutions for this kind of problem, one is called the angular-deferred-bootstrap and another is a solution I made just a month ago. Both are making use of the AngularJS lifecycle in manually bootstrapping the application, using angular.bootstrap(). Note that when you are manually bootstrapping your application you need to remove the ng-app directive.
As you will see i'm new in AngularJS, JS and in web development at all =) really sorry for that but i try to.
I try to build a massive webform (about 200 different fields) with AngularJS controllers. I need access from controller to root data source. AngularJS team ask do not make Services just for storing data, but i want to make service for load and save data (at start into .json files on a server).
Service:
AppName.factory('MasterData', ['$rootScope', '$http', '$q', '$log',
function($rootScope, $http, $q, $log) {
var responseData;
$http.get('/getdata.php').then(function (response) {
responseData = response.data;
console.log(response.data);
});
return responseData;
}]);
Controller:
AppName.controller('justController', ['$scope', 'MasterData', '$log',
function ($scope, MasterData, $log) {
$scope.data = MasterData.justControllerSectionData;
console.log(MasterData);
}
]);
Controller return undefined. But console.log from service returns the object.
I feel that the problem is too easy, but i can't find how to solve it :(
Also i can't use function like .getData() from controller to service because it ask the data from server each time any controller loads. I have the routes in AngularJS app with 12-14 controllers (full webform divided by sections) and i think it is good to get the data from backend once.
P.S. I think there is problem with promises, but when i try to use code like this:
var defer = $q.defer();
$http.get('/getdata.php').success(function(data){
defer.resolve(data);
});
return defer;
I've got object with resolve, reject and so on. And really can't understand what can i do with it :(
Help me to get the data in controller :)
Your code doesn't work, because the callback you supplied to success() in your service is called asynchronously; after your service has returned, that is:
The sequence is like this:
The function in MasterData is run. The $http.get request is launched and attached the promise callback. responseData is referenced in this callback (aka. "closed over").
The function returns from the service to your controller. responseData has not been set yet, which doesn't stop the parent scope function from returning.
$http.get succeeds and responseData is set in the service however unreachable for the controller.
If the scoping of the nested function in success() is not clear to you, I'd recommend reading about closures in JavaScript (or even better, in general), for example here.
You can achieve your goal with a service like this:
function($q, $http, /* ... */) {
return {
getData: function() {
var defer = $q.defer();
$http.get('/getdata.php', { cache: 'true'})
.then(function(response) {
defer.resolve(response);
});
return defer.promise;
};
}
The $http service will happily cache your response data, so you don't have to. Note that you need to retrieve the promise from your deferred object to make this work.
The controller is like this:
/* omitted */ function($scope, YourService) {
YourService.getData().then(function(response) {
$scope.data = response.data;
});
}
Since success is depreciated, I modified success to then.
Services should return the promise rather than the data. This is the asynchronous way.
First fetch the value in the Angular's run method. For this example I put it in the $rootScope since that makes it accessible to all scopes in all controllers.
AppName.run(['$http', '$rootScope',
function($http, $rootScope) {
console.log('Run');
$http.get('http://api.geonames.org/citiesJSON?north=44.1&south=-9.9&east=-22.4&west=55.2&lang=de&username=demo')
.success(function(data) {
$rootScope.resource = data;
console.log($rootScope.resource);
});
}
])
This is not really necessary unless you store it in some weird place.
AppName.service('Resource',['$rootScope',
function($rootScope) {
return $rootScope.resource;
}
]);
Every scope will inherit the values in the $rootScope (thats why the service really isn't necessary.
AppName.controller('mainController', ['$scope', 'Resource',
function($scope, Resource) {
console.log('controller');
console.log(Resource);
}
]);
Warning!!! This value will not be available until after the first controller loads. If you use it in the controller just remember that you can bind it to html but the first pass through the controller code will not have initialized the variable yet.
I'm sure there is an easy way to do what I want, I just cant wrap my head around it. How can I get the http interceptor in angular to retry a request if it fails? I imagine I would have to build some sort of promise in the request right? Then in the response I would have to check whether the response was an error and if so, do the promise? How is that done? I have been trying to adapt the example here: http://docs.angularjs.org/api/ng.$http
The reason I am trying to use an interceptor is because I need to add the token and a few other things to the request url, as well as some things to handle xdr's.
Here's a $http interceptor that (immediately) replays timed out request(s) (i.e. response status 0). There were two non-obvious (to me!) elements to constructing this example:
How to call $http from within the interceptor - simply adding $http to the dependency list didn't work as angular complains of a circular dependency
How to reference the original request from the response object in order to retry it
This answer addresses both topics but is more involved so I include a simplified version below.
joinApp.factory('httpResponseErrorInterceptor',function($q, $injector) {
return {
'responseError': function(response) {
if (response.status === 0) {
// should retry
var $http = $injector.get('$http');
return $http(response.config);
}
// give up
return $q.reject(response);
}
};
});
joinApp.config(function($httpProvider) {
$httpProvider.interceptors.push('httpResponseErrorInterceptor');
});
In your actual implementation, you would likely want more sophisticated http response code processing, a limit to the number of times you retry, etc.
For the sake of completeness, here is a version of mygzi's answer with retry delay:
.factory('httpResponseErrorInterceptor', ['$injector', '$q', '$timeout', function($injector, $q, $timeout) {
return {
'responseError': function(response) {
if (response.status === 0) {
return $timeout(function() {
var $http = $injector.get('$http');
return $http(response.config);
}, 15000);
}
return $q.reject(response);
}
};
}])
.config(function($httpProvider) {
$httpProvider.interceptors.push('httpResponseErrorInterceptor');
});
$timeout returns a promise that is completed with what is returned from the function parameter, so we can conveniently just return the $http call wrapped in $timeout.
I've done this for an app I wrote before. To do the retry wrap the $http usage in a function (or preferably in a service so that you can reuse it easily). If the request fails, call the function again.
The trick is then to pass the promise object along with each request. If you create a new promise with each request then it won't match the one you returned to the original caller, so the original caller won't get his promise resolve once the request passes.
So it is something like this (note that the defer object is passed along in each retry):
app.service('HttpService', ['$q', function($q) {
this.makeRequest = _makeRequest;
function _makeRequest(url, data, deffered) {
// We want to keep the same promise for each request, so we don't loose track
if (deferred === undefined) {
deferred = $q.defer();
}
// Now make the request
$http({...}).success(...).error(
function(){
// If some condition
_makeRequest(url, data, deffered);
}
)
// Lastly return the promise
return deferred.promise;
}
}])