I'm new to Angular, and am working on an interceptor. I created an angular factory to get some data from an API like that :
app.factory('Connection',['$resource',function($resource) {
return $resource('url',{param1: '1',param2: '55'},);
}]);
I also created the interceptor which looks like that :
app.factory('connectionInterceptor', function($q,$location) {
var connectionInterceptor = {
response: // code here
responseError: // code here
};
return connectionInterceptor;
});
The interceptor works well. But it intercepts every http request I do, and I'd like to make it work for a specific $resource. I read in angular $resource doc that there is a way to make it by adding an interceptor action/param to $resource. So I tried :
app.factory('Connection',['$resource',function($resource) {
return $resource('http://localhost:8080/api/login',{user: '1',password: '55'}, {},
query: {
method : 'GET',
interceptor : 'connectionInterceptor'
}
});
}]);
which didn't work. The thrown error is : Error in resource configuration for action query. Expected response to contain an object but got an array.
What did I miss ?
As you said, interceptors are globally set. I had to add a test to my response to check the $resource URL and add some specific treatment.
module.factory('interceptor', function() {
var interceptor = {
response: function(response) {
if (response.config.url.startsWith('my url')) {
// some treatment
}
else
// other treatment
return response;
}
return connectionInterceptor;
});
Related
I have been looking at an application I made a while back and there is a particular page where the details are being loaded last. Because of this, it seems to be queuing the request (there are more than 6 others before it) and that is causing the page to be slow.
I figured I could find a solution to prioritize these requests and I found this:
How to prioritize requests in angular $http service?
So I created my version of it and added it to my interceptors:
// Add our auth interceptor to handle authenticated requests
$httpProvider.interceptors.push('authInterceptor');
$httpProvider.interceptors.push('httpPriorityInterceptor');
The interceptor looks like this:
function factory($injector, $q) {
var requestStack = [], // request stack
$http = null; // http service to be lazy loaded
return {
request: request,
responseError: responseError
};
//////////////////////////////////////////////////
function request(config) {
// Lazy load $http service
if (!$http) {
$http = $injector.get('$http');
}
if (!config.hasBeenRequested) {
config.hasBeenRequested = true;
config.priority = config.priority || 3;
console.log(config);
// add a copy of the configuration
// to prevent it from copying the timeout property
requestStack.push(angular.copy(config));
// sort each configuration by priority
requestStack = requestStack.sort(sort);
// cancel request by adding a resolved promise
config.timeout = $q.when();
}
// return config
return config;
}
function responseError(rejection) {
// check if there are requests to be processed
if (requestStack.length > 0) {
requestStack.reduceRight(function(promise, config) {
return promise.finally(function() {
return $http(config);
});
}, $q.when());
requestStack.length = 0;
}
// return rejected request
return $q.reject(rejection);
}
//////////////////////////////////////////////////
function sort(config1, config2) {
return config1.priority < config2.priority;
}
}
The problem is, it seems to be intercepting template requests too. I have no issue with that, but they are not resolving. Instead I get a lot of errors:
Error: [$templateRequest:tpload] Failed to load template: app/accounts/accounts.html (HTTP status: -1 )
Has anyone encountered this before? Is there something I can do to fix this?
you should know that every request such as html files , css file and ... comes into interceptor.
in your case you dont need to prioritize this files. so you can filter your request like:
if (config.url.toString().toLowerCase().includes("api")) {
//place your functionality
}
How do I add interceptors to a $resource call?
Let's say I have a resource factory called Users, like so;
app.factory('Users', ['$resource', 'resourceInterceptor',
function ($resource, resourceInterceptor) {
return $resource(
'users/:user_id',
{
user_id: '#id'
},
{
query: {
method: 'GET', // Not changing the default method, just adding an interceptor
interceptor: resourceInterceptor // Is there any better way to do this.. like globally?
},
save: {
method: 'POST', // Same here
interceptor: resourceInterceptor // Again...
},
..., // And so on
}
);
}]);
and my resourceInterceptor service looks like;
app.factory('resourceInterceptor', ['$rootScope',
function ($rootScope) {
return {
request: function () {
// This function isn't executed at all?
$rootScope.loading = true;
},
response: function () {
$rootScope.loading = false;
},
responseError: function () {
$rootScope.loading = false;
}
};
}]);
First of all, the request intercept function is never executed, why not?
Secondly, having to hardcode the interceptor to existing $resource methods is very tedious , is there a way to easier assign interceptors to specific $resource calls, or maybe even assign an interceptor to all $resource calls?
To use an interceptor in a resource you should:
1 - Make an httpInterceptor with you request, response, responseError:
app.factory('myInterceptor', function () {
//Code
//return { request:...,
});
2 - Config this Interceptor in your app:
app.config(['$httpProvider', function ($httpProvider) {
$httpProvider.interceptors.push('myInterceptor');
}]);
Right now as you have config your httpProvider to has an interceptor wherever you inject $http you will use this provider so... you will excute your request, response and responseError funciton.
3 - Using it in a resource.
As $resource use $http and you have config a httpProvider globaly you will call your interceptors' func when you use your resource.
Second question:
You can not set an interceptor to a concrete $http object, they (interceptors) are set globally.
(Even if you set the interceptor before your module definition and then you remove it, you can not know the execution order)
What you can do if you do not want to override the interceptor property in each $resource action (as you write in your question) you can improve your interceptor.
app.factory('userLoadingInterceptor', function () {
//Code
return {
request: function(){
//Check if your are working with a url related with users
// and if so do things...
}
});
From the docs:
The interceptor object has two optional methods - response and responseError
I don't know what you want to achieve but generic HTTP interceptors might be an alternative.
A generic HTTP Interceptor should do what you want. You can find a sample here: Handle HTTP 302 response from proxy in angularjs.
I'm willing to retrieve the response header of a resource request, cause I've put pagination information and something else in it rather than the response body, to make the REST api clear.
Though we can get it from the success / error callback like below:
Object.get({type:'foo'}, function(value, responseHeaders){
var headers = responseHeaders();
});
Where 'Object' is my resource factory service.
Further, when I'm trying to make the route change after required resources resolved, I've tried this:
.when('/list', {
templateUrl: 'partials/list.html',
controller: 'ListCtrl',
// wait for the required promises to be resolved before controller is instantialized
resolve: {
objects: ['Object', '$route', function(Object, $route){
return Object.query($route.current.params).$promise;
}]
}
})
and in controller, just inject "objects" instead of Object service, because it's resolved and filled in with real data.
But I got problem when I try to get headers info from the "objects" in controller.
I tried objects.$promise.then(function(data, responseHeaders){}), but responseHeader was undefined.
How can I change the $resource service's behavior so that it throws the responseHeader getter into the $promise then() callback function?
My service "Object" for reference:
myServices.factory('Object', ['$resource',
function($resource){
return $resource('object/:id', {id: '#id'}, {
update: {method: 'PUT'},
});
}
]);
I had the exact same problem. I used an interceptor in the resource definition to inject the http headers in the resource.
$resource('/api/resource/:id', {
id: '#id'
}, {
index: {
method: 'GET',
isArray: true,
interceptor: {
response: function(response) {
response.resource.$httpHeaders = response.headers;
return response.resource;
}
}
}});
Then, in the then callback, the http headers are accesible through $httpHeaders:
promise.then(function(resource) {
resource.$httpHeaders('header-name');
});
I think I had a similar problem: After POSTing a new resource I needed to get the Location header of the response, since the Id of the new resource was set on the server and then returned via this header.
I solved this problem by introducing my own promise like this:
app.factory('Rating', ['$resource',
function ($resource) {
// Use the $resource service to declare a restful client -- restangular might be a better alternative
var Rating = $resource('http://localhost:8080/courserater/rest/ratings-cors/:id', {id: '#id'}, {
'update': { method: 'PUT'}
});
return Rating;
}]);
function RestController($scope, $q, Rating) {
var rating = new Rating();
var defer = $q.defer(); // introduce a promise that will be resolved in the success callback
rating.$save(function(data, headers){ // perform a POST
// The response of the POST contains the url of the newly created resource
var newId = headers('Location').split('/').pop();
defer.resolve(newId)
});
return defer.promise;
})
.then (function(newId) {
// Load the newly created resource
return Rating.get({id: newId}).$promise; // perform GET
})
.then(function(rating){
// update the newly created resource
rating.score = 55;
return rating.$update(); // perform PUT
});
}
We can't use .then for returning the header because the promise doesn't allow for multiple return values. (e.g., (res, err))
This was a requested feature, and was closed https://github.com/angular/angular.js/issues/11056
... the then "callbacks" can have only [one] argument. The reason for this is that those "callbacks" correspond to the return value / exception from synchronous programming and you can't return multiple results / throw multiple exceptions from a regular function.
I'm trying to figure out if it is possible to use a $http interceptor to cancel a request before it even happens.
There is a button that triggers a request but if the user double-clicks it I do not want the same request to get triggered twice.
Now, I realize that there's several ways to solve this, and we do already have a working solution where we wrap $http in a service that keeps track of requests that are currently pending and simply ignores new requests with the same method, url and data.
Basically this is the behaviour I am trying to do with an interceptor:
factory('httpService', ['$http', function($http) {
var pendingCalls = {};
var createKey = function(url, data, method) {
return method + url + JSON.stringify(data);
};
var send = function(url, data, method) {
var key = createKey(url, data, method);
if (pendingCalls[key]) {
return pendingCalls[key];
}
var promise = $http({
method: method,
url: url,
data: data
});
pendingCalls[key] = promise;
promise.finally(function() {
delete pendingCalls[key];
});
return promise;
};
return {
post: function(url, data) {
return send(url, data, 'POST');
}
}
}])
When I look at the API for $http interceptors it does not seem to be a way to achieve this. I have access to the config object but that's about it.
Am I attempting to step outside the boundaries of what interceptors can be used for here or is there a way to do it?
according to $http documentation, you can return your own config from request interceptor.
try something like this:
config(function($httpProvider) {
var cache = {};
$httpProvider.interceptors.push(function() {
return {
response : function(config) {
var key = createKey(config);
var cached = cache[key];
return cached ? cached : cached[key];
}
}
});
}
Very old question, but I'll give a shot to handle this situation.
If I understood correctly, you are trying to:
1 - Start a request and register something to refer back to it;
2 - If another request takes place, to the same endpoint, you want to retrieve that first reference and drop the request in it.
This might be handled by a request timeout in the $http config object. On the interceptor, you can verify it there's one registered on the current request, if not, you can setup one, keep a reference to it and handle if afterwards:
function DropoutInterceptor($injector) {
var $q = $q || $injector.get('$q');
var dropouts = {};
return {
'request': function(config) {
// I'm using the request's URL here to make
// this reference, but this can be bad for
// some situations.
if (dropouts.hasOwnProperty(config.url)) {
// Drop the request
dropouts[config.url].resolve();
}
dropouts[config.url] = $q.defer();
// If the request already have one timeout
// defined, keep it, othwerwise, set up ours.
config.timeout = config.timeout || dropouts[config.url];
return config;
},
'requestError': function(reason) {
delete dropouts[reason.config.url];
return $q.reject(reason);
},
'response': function(response) {
delete dropouts[response.config.url];
return response;
},
'responseError': function(reason) {
delete dropouts[reason.config.url];
return $q.reject(reason);
}
};
}
So I have a bunch of controllers that do $http requests
but in every $http request i have a .error(function(data...){//always the same})
How could I build an.. "abstract class" for $http?
This here would be the always repeating code
.error(function(){
$scope.flashes = {
server: {
type: "danger",
message: "There was a server error processing your request. Please try again later."
}
};
})
I add the same concern few weeks ago and i came up with this solution :
I first created a custom service intercepting every http requests made :
.factory('HttpInterceptor', ['$q', '$rootScope', function($q, $rootScope) {
return {
// On request success
request : function(config) {
// Return the config or wrap it in a promise if blank.
return config || $q.when(config);
},
// On request failure
requestError : function(rejection) {
//console.log(rejection); // Contains the data about the error on the request.
// Return the promise rejection.
return $q.reject(rejection);
},
// On response success
response : function(response) {
//console.log(response); // Contains the data from the response.
// Return the response or promise.
return response || $q.when(response);
},
// On response failure
responseError : function(rejection) {
//console.log(rejection); // Contains the data about the error.
//Check whether the intercept param is set in the config array. If the intercept param is missing or set to true, we display a modal containing the error
if (rejection.config && typeof rejection.config.intercept === 'undefined' || rejection.config.intercept)
{
//emitting an event to draw a modal using angular bootstrap
$rootScope.$emit('errorModal', rejection.data);
}
// Return the promise rejection.
return $q.reject(rejection);
}
};
}]);
I also defined a custom config property 'intercept' that i can add to the $http config object. It is useful when I don't want to apply this behavior on a particular request.
E.g :
var registerResource = $resource('/registration/candidate/register', {}, {query:
{method:'POST', isArray: false, intercept: false }
});
In order the have a flexible solution, it is also important to not forget to do :
return $q.reject(rejection);
So you can still use the error callback on your promise in your controller if you want to combine both ways (interception + manual handling)
Finally, I added this service to my application :
app.config(['$httpProvider', function($httpProvider) {
// Add the interceptor to the $httpProvider to intercept http calls
$httpProvider.interceptors.push('HttpInterceptor');
}]);
I simplified the service but you can also use it for many things. Personally, I also use it to :
Make sure to not fire duplicate http requests (if the user click a lot on a submit button).
Draw an alert at the beginning of an http call and close it at the end to inform the user that is treatment is processing (export of data for instance).
PS: The official documentation mention this interceptor
You could do something like this:
app.service('myHttp', function($http){
return function($scope, httpParameters){
var httpPromise = $http(httpParameters);
httpPromise.error(function(){
$scope.flashes = {
server: {
type: "danger",
message: "There was a server error"
}
}
});
};
});
app.controller('MainCtrl', function($scope, myHttp) {
myHttp($scope, {method: 'GET', url: 'www.google.com'});
});