how to prevent duplicated $http requests in AngularJS app? - angularjs

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;
}
}
}
]);
}

Related

Angular pattern for ensuring single active promise

In our application, we have an search input field. Typically a request is sent while the user types (a la Google Instant) and the results are displayed.
Obviously, the following can happen:
User types, which results in ajaxRequest1
User continues typing, resulting in ajaxRequest2
results2 corresponding to ajaxRequest2 are received and displayed
After this, results1 corresponding to ajaxRequest1 are received. Obviously, since ajaxRequest2 was sent after ajaxRequest1, we only care about results2, not results1.
EDIT: The obvious answer here is "Use debounce". For reasons of confidentiality and brevity, I'll just say here that it won't work in our particular scenario. I know what debounce does and I have considered it.
In pseudo-code, we used to handle it like this:
$scope.onInput = function() {
var inputText = getInput();
SearchService.search(inputText).then(function(results) {
// only display if input hasn't changed since request was sent
if(inputText === getInput()) {
displayResults(results);
}
});
};
Since this involves a lot of boilerplate and looks ugly, we moved to a pattern where the SearchService manages things a bit better
$scope.onInput = function() {
var inputText = getInput();
SearchService.search(inputText).then(function(results) {
displayResults(results);
});
}
function SearchService() {
var cachedSearchDeferred;
this.search = function(inputText) {
if(cachedSearchDeferred) {
//means there's an unresolved promise corresponding to an older request
cachedSearchDeferred.reject();
}
var deferred = $q.deferred();
$http.post(...).then(function(response) {
// saves us having to check the deferred's state outside
cachedSearchDeferred = null;
deferred.resolve(response.data);
});
cachedSearchDeferred = deferred;
return deferred.promise;
}
}
This works fine. The SearchService creates a deferred containing the promise corresponding to the most recent call to SearchService.search. If another call is made to SearchService.search the old deferred is rejected and a new deferred is created corresponding to the new call.
Two questions:
Is this a good pattern to do what we need - essentially request locking? We want to ensure that only the most recent request's promise resolves successfully
If we had other SearchService methods that needed to behave similarly, then this deferred boilerplate needs to be inside every method. Is there a better way?
#Jayraj depends how sophisticated you want to make your http api. You can go very deep, but if I understand your question you are looking for a http timeout interceptor. Using Angular $httpProvider you can register a custom interceptor which needs to return a response and request.
I should note I've frankensteined this from pieces of different code bases so I don't take credit for code, but it is early morning and would need to go find the source in my libraries, but to help best practice directionally here goes.
ANGULAR.JS EXAMPLE
angular team give this example
$httpProvider.interceptors.push(function($q, dependency1, dependency2) {
return {
'request': function(config) {
// same as above
},
'response': function(response) {
// same as above
}
};
});
create a factory object that holds you http endpoint configuration i.e a config file that with a server component and an endpoint that identified the UID for the endpoint i.e. where does it go and who is sending it
(function() {
'use strict';
var config = {
server: {
url: null
},
endpoint: {
url: null,
uuid: null,
}
};
return angular.module('matrixme.config', [
]).constant('config', config);
})();
for brevity sake I will leave out the service provider code, but you will need to build an REST api service provider, which you then inject into all relevant classes. The provider will effectively configure your config object e.g. user, articles and will serve as home for api calls.
You create your own interceptor and inject as such:
(function() {
'use strict';
angular.module('matrixme.api', ['matrixme.config'])
.config(['$httpProvider', function($httpProvider) {
$httpProvider.interceptors.push('timeoutInterceptor');
}]);
})();
Build the injector before you inject :) I have not tested this but really answering your question of best practice. So this is directional, but you would then create your request and response. You can build multiple custom interceptors e.g. uuid, auth timeout, etc.
(function() {
'use strict';
TimeoutInterceptor.$inject = ['$timeout', '$q', '$rootScope', 'request'];
function TimeoutInterceptor($timeout, $q, $rootScope, request) {
return {
request: function(config) {
if ((config.url)) {
config._ttl = config._ttl ? Math.min(2000, config._ttl * 2) : 2000;
config.timeout = $timeout(function() {
config._isTimeout = true;
}, config._ttl);
}
return config;
},
response: function(response) {
if (response.config.timeout) {
$timeout.cancel(response.config.timeout);
$rootScope.serverStatus = 0;
}
return response;
},
};
}
angular.module('matrixme.api')
.factory('timeoutInterceptor', TimeoutInterceptor);
})();
It turns out there already exists a solution for this: RxJS. The example in their README is almost this exact scenario.
const $input = $('#input');
/* Only get the value from each key up */
var keyups = Rx.Observable.fromEvent($input, 'keyup')
.pluck('target', 'value')
.filter(text => text.length > 2 );
/* Now debounce the input for 500ms */
var debounced = keyups
.debounce(500 /* ms */);
/* Now get only distinct values, so we eliminate
the arrows and other control characters */
var distinct = debounced
.distinctUntilChanged();
/* Once that is created, we can tie together the
distinct throttled input and query the service.
In this case, we'll call flatMapLatest to get
the value and ensure we're not introducing any
out of order sequence calls. */
const suggestions = distinct
.flatMapLatest(() => {
// Do XHR and return a promise
// flatMapLatest will always use the latest one
});
There's also RxJS for Angular which adds things to the $scope object.

Registering a HTTP-Interceptor in a lazy loaded Module

Good Morning,
I've run into a problem I seem to not be able to solve myself.
I'm trying to register a httpinterceptor in a different place than the initial config block.
Doing this works as expected:
angular.module('app', ['ngResource'])
.config(function($httpProvider) {
$httpProvider.interceptors.push(function() {
return {
request: function($request) {
console.log($request);
return $request;
}
}
})
})
But the interceptor is part of a submodule. So my app looks as follows:
EDIT:
angular.module('app', ['ngResource']);
angular.module('app.submodule', ['ngResource'])
.config(function($httpProvider) {
$httpProvider.interceptors.push(function() {
return {
request: function($request) {
console.log($request);
return $request;
}
}
})
})
Additionally the submodule is lazyloaded. But I do not think that is the root of the problem.
I tried referencing the httpProvider in the initial config block by adding:
$httpProviderReference = $httpProvider;
So I could register an interceptor later. This wouldn't even work in the initial run() block. Inspecting the Provider, it seems to have worked, but the interceptor is never called.
Does anyone know how to work around this? I am trying to add an authentication token to the header of a request.
Thanks in advance, olu
For anyone else encountering a similar problem, this is how i have done it:
I registered a general interceptor in my main module:
angular.module('app', ['ngResource'])
.config(function($httpProvider) {
$httpProvider.interceptors.push('Interceptors');
})
.factory('Interceptors', function() {
var requestFunctions = [];
return {
request: function($request) {
var r = $request;
for (var i = requestFunctions.length - 1; i >= 0; i--) {
r = requestFunctions[i]($request);
};
return $request;
},
setRequestFunction: function(fn) {
requestFunctions.push(fn);
}
}
})
The factory holds an array of functions, that are called in series. The original request is parsed through the functions. The request is altered in the submodule:
angular.module('app.submodule', ['ngResource'])
.run(function($injector,MyService) {
var Interceptors = $injector.get('Interceptors');
Interceptors.setRequestFunction(function($request) {
$request.headers['auth-id'] = MyService.getAuthData().authId;
$request.headers['auth-token'] = MyService.getAuthData().authToken;
}
return $request;
})
})
Also Restangular is worth having a look at. It is a substitution for ngResource and it seems to be possible to register interceptors for Restangular anywhere in the app.

Is it possible to add GET/POST parameters to Angular $http requests with an interceptor

Say I need to include a GroupId parameter to every request the user makes, but I don't want to modify every service call to include that. Is it possible to make that GroupId appended automatically to all requests, whether it is POST or GET query string?
I have been looking into the interceptor request function, but can't figure out how to make the change
** Edit **
Current working sample below is a combo of Morgan Delaney and haimlit's suggestions (I think it is a combom anyway). The basic idea is that if the request is a POST, modify config.data. For GET, modify params. Seems to work so far.
Still not clear on how the provider system works in Angular, so I am not sure if it is entirely approriate to modify the data.params properties here.
.config(['$httpProvider', function ($httpProvider) {
$httpProvider.interceptors.push(['$rootScope', '$q', 'httpBuffer', function ($rootScope, $q, httpBuffer) {
return {
request: function (config) {
if (config.data === undefined) {
//Do nothing if data is not originally supplied from the calling method
}
else {
config.data.GroupId = 7;
}
if (config.method === 'GET') {
if (config.params === undefined) {
config.params = {};
}
config.params.GroupId = 7;
console.log(config.params);
}
return config;
}
};
} ]);
} ]);
If your example works, great. But it seems to lack semantics IMHO.
In my comments I mentioned creating a service but I've set up an example Plunker using a factory.
Plunker
Relevant code:
angular.module( 'myApp', [] )
.factory('myHttp', ['$http', function($http)
{
return function(method, url, args)
{
// This is where the magic happens: the default config
var data = angular.extend({
GroupId: 7
}, args );
// Return the $http promise as normal, as if we had just
// called get or post
return $http[ method ]( url, data );
};
}])
.controller( 'myCtrl', function( $scope, $http, myHttp )
{
// We'll loop through config when we hear back from $http
$scope.config = {};
// Just for highlighting
$scope.approved_keys = [ 'GroupId', 'newkey' ];
// Call our custom factory
myHttp( 'get', 'index.html', { newkey: 'arg' }).then(function( json )
{
$scope.config = json.config;
});
});

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.

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