Hi have 4 directives on the page, all directive require list of user.
User List is stored in database which require hhtp request.
As 4 directive are on the page at same time so 4 different ajax call has been sent to server for similar response.
And then the response is getting cached.
What can be done that all 4 directive recieves its user list and only one ajax is sended to server.
Code Inside Directive(self is this) (ajaxCallService is service)
ajaxCallService.getUser(function(response) {
self.users = response;
//Next operations
});
ajaxCallService Service
Variable
var userList = []
Method
if (!userList.length) {
$http.post({
url: #,
data:#
})
.then(
function(response) {
userList = response.allMembers;
callback && callback(userList);
}
);
}else{
callback && callback(userList);
}
How can i prevent 4 ajax calls and only make 1 call and let other 3 wait for the response and pass response back?
You can use promises for this, to see if it is already running. Because services are singletons in angular, you know it is always a shared instance:
var userList = [];
var promise;
function getUser() {
// inject the $q service
var deferred = $q.defer();
if (userList.length) {
// already got data, immediately resolve that one
deferred.resolve(userList);
}
if (promise) {
// a promise is already working on it, return this one
return promise;
} else {
$http.post(...).success(function(response) {
userList = response;
deferred.resolve(response);
});
}
// if this point is reached, this is the first call
// assign the promise so other calls can know this one is working on it
promise = deferred.promise;
return promise;
}
Related
Hey on first $http requests I save my results in cache i am using two fucnctions in my controller calling the same function in service where is calling the $Http at first function for $Http it save the results in cache but for the second function when i tried to test it out wehter cache is empty or not it should not be empty as i already save results in my cache but it gives me
undefined error
Can Anyone tell what's going on wrong on my code
Here is the controller
vm.getTopHotels = function(){
var hotelsLimit = 10;
var top_hotels =
dataService.getHotels()
.then(
function(hotels){
console.log(hotels);
sortHotels = commonMethods.sortHotels(hotels.data.data,'Rating','SORT_DESC');
hotelDetailsCheck = checkDetailsIfExists(sortHotels);
//Get only top 10 hotels for home page
top_hotels = hotelDetailsCheck.slice(0,10);
vm.topHotels = top_hotels;
},
function(err){
console.log(err);
});
};
vm.getTopHotels();
vm.getRHotels = function(){
dataService.getHotels()
.then(function(hotels){
console.log('hotels recieced 2 ');
},
function(err){
console.log(err);
});
}
vm.getRHotels();
**dataService is Facotry here that is calling the $http methods **
for the vm.getTopHotels I'm saving results in the cache so getRHotels when call the $Http i am chcecking that if the cache is not empty it should retreive the data from the cache if not then it call the $Http request but for this function too it is calling the $http why? because i have already save the results in cache Can anybody tell me what is wrong?
Here is the dataService Code which is calling the $http methods and saving in Cache
(function(){
angular
.module('app')
.factory('dataService',DataFactory);
DataFactory.$inject = ['$http','$q','$cacheFactory']
function DataFactory($http,$q,$cacheFactory){
var cache = $cacheFactory('localCache');
var service = {
getHotels:getHotels
};
return service;
function getHotels(){
var def = $q.defer();
var hotelsData = cache.get('hotelsData');
console.log(hotelsData);
if(!hotelsData){
$http.get('/hotels/getHotelsData')
.then(
function successCallback(data){
cache.put('hotelsData',data.data);
// service.hotels = data.data;
def.resolve(data);
},
function errorCallback(data){
def.reject('Failed to retrieve hotels');
});
return def.promise;
}else{
console.log('cached');
}
}
}
})();
You can actually specify $http to cache results by adding cache:true to your config object.
function getHotels() {
return $http.get('/hotels/getHotelsData', {cache:true});
}
You can read more about the $http config here:https://docs.angularjs.org/api/ng/service/$http
Also to clarify, $q.defer is a helper that alows you to wrap non promise API callbacks as promises. $http returns a promise. You can just return the response of $http.get and perform a .then on it.
If you need to manipulate the data before returning it, you still don't need to wrap it within $q.defer()
function getHotels() {
return $http.get('/hotels/getHotelsData', {cache:true})
.then(function(response){
response.data[0].hotelName = 'changedName';
return response;
})
}
The getHotels function has a race condition: A second call to the function before the data returns from the server will allow a second HTTP GET request.
Since the $http service immediately returns a promise, it is better to cache that promise.
var hotelsPromise = null;
function getHotels(){
if (hotelsPromise) return hotelsPromise;
//ELSE
hotelsPromise = $http.get('/hotels/getHotelsData')
.then(function successCallback(response){
return response.data;
},
function errorCallback(response){
throw 'Failed to retrieve hotels';
});
return hotelsPromise;
}
This will avoid erroneous multiple HTTP GET requests.
I was learning angular interceptors today. I did some samples to to better understand the concept. Here is small sample.
var app = angular.module("myApp", []);
app.factory("timestampMaker", [
function() {
var timestampMaker = {
request: function(config) {
console.log(config);
config.requestTimestamp = new Date().getTime();
return config;
},
response: function(response) {
response.config.responseTimestamp = new Date().getTime();
return response;
}
};
return timestampMaker;
}
]);
app.config(['$httpProvider',
function($httpProvider) {
$httpProvider.interceptors.push('timestampMaker');
}
]);
app.run(function($http) {
$http.get('https://api.github.com/users/naorye/repos').then(function(response) {
console.log(response);
var time = response.config.responseTimestamp - response.config.requestTimestamp;
console.log("The request took" + (time / 1000) + "seconds")
});
});
<html ng-app="myApp">
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
</head>
</html>
When I am doing console.log(config) inside request function, here is the output on the console.
I am not getting how responseTimestamp appear in the config object of request where as its defined inside response function
Note I used the source code for 1.2.x which is the one added in the question.
$httpProvider is a provider that gives you $http service.
As a provider, in config time exposes a public array - there you just add all strings from services names you wanted to be injected in your response/requests.
That is what you do here
app.config(['$httpProvider',
function($httpProvider) {
$httpProvider.interceptors.push('timestampMaker');
}
]);
and it can be only done in config time because per docs providers can be configured before the application starts
You can see the exposed array in the source code in line 159
var responseInterceptorFactories = this.responseInterceptors = [];
When you request the $http service, injecting it into your service/controller,
$get function is executed. In that function, your array of interceptors is iterated, as you can see in source code in line 179
var reversedInterceptors = [];
forEach(responseInterceptorFactories, function(interceptorFactory, index) {
var responseFn = isString(interceptorFactory) ? $injector.get(interceptorFactory) : $injector.invoke(interceptorFactory);
/**
* Response interceptors go before "around" interceptors (no real reason, just
* had to pick one.) But they are already reversed, so we can't use unshift, hence
* the splice.
*/
reversedInterceptors.splice(index, 0, {
response: function(response) {
return responseFn($q.when(response));
},
responseError: function(response) {
return responseFn($q.reject(response));
}
});
});
Per convention, they reverse the order. You can see that using the string, they get a reference to the function using $injector.get or $injector.invoke, and using $q.when() we can introduce them to the promises chain, even if they are synchronous code - See this Can I use $q.all in AngularJS with a function that does not return a .promise? if you are not sure what I meant about $q.when()
So far we have an array with functions, which are all promise-like (thanks to $q.when()). When you request data through $http like this
$http.get('https://api.github.com/users/naorye/repos').then(function(response) {
console.log(response);
var time = response.config.responseTimestamp - response.config.requestTimestamp;
console.log("The request took" + (time / 1000) + "seconds")
});
even though you have the .get(), is just a shortcut for all the same functionality which is here
In the code the relevant part is this one:
First, a chain array is created with two values: an inner function which is not important for our purpose (but it returns a promise - this is important), and undefined value.
Our array with interceptors is iterated, and request interceptors are added at the beginning (before the request) and response at the end. Like this
function serverRequest {
// some code
};
var chain = [serverRequest, undefined];
var promise = $q.when(config);
// apply interceptors
forEach(reversedInterceptors, function(interceptor) {
if (interceptor.request || interceptor.requestError) {
chain.unshift(interceptor.request, interceptor.requestError);
}
if (interceptor.response || interceptor.responseError) {
chain.push(interceptor.response, interceptor.responseError);
}
});
then, having the chain complete (remember our array of interceptors was full of promises), the code iterates it, adding all of them using .then(), causing all of them to be executed in a chain, following promises chaining (you can google that)
while(chain.length) {
var thenFn = chain.shift();
var rejectFn = chain.shift();
promise = promise.then(thenFn, rejectFn);
}
Finally, the callback you add in success and error is added at the very end of the chain and the promise is returned
promise.success = function(fn) {
promise.then(function(response) {
fn(response.data, response.status, response.headers, config);
});
return promise;
};
promise.error = function(fn) {
promise.then(null, function(response) {
fn(response.data, response.status, response.headers, config);
});
return promise;
};
return promise;
The only part I am not sure about is the use of undefined in the chain array
Summary
You add the name for your services, $HttpProvider uses $invoke service to get them and adds them in the promise chain using $q.when(), returning a promise. At the end of that, your callbacks for a specific $http request is added.
I try to get some important things like: companyid,employeeid etc. with every request that a user makes. So this has to be received before everything else is done.
After that the user receives information based on his companyid that he sets with every request (get/company/{companyid}).
The problem that I have is that the response for receiving the companyid takes to long and angular already tries to make a request to (get/company/{companyid}) obviously there is no companyid yet.
I've tried to fix this whit promise but it's not working.
Here I try to receive some important information about the user(that I do with every request) :
Service
(function () {
angular.module('employeeApp')
.service('authenticationservice', authenticationservice);
function authenticationservice($http,$location,authenticationFactory,$q,GLOBALS,$cookies) {
this.validateUser = function () {
var vm = this;
vm.deferred = $q.defer();
data = {"api_token": api_token};
return $http.post(GLOBALS.url+'show/employee/' + $cookies.get('employeeid'),data)
.success(function(response)
{
vm.deferred.resolve(response);
})
.error(function(err,response)
{
vm.deferred.reject(err);
});
return vm.deferred.promise;
}
}
})();
Routes file
(In my routes file I use the authenticationservice to set all important users variables.)
employeeAppModule.run([
'authenticationservice',
'constants',
function(authenticationservice,constants) {
authenticationservice.validateUser()
.then(function(response)
{
constants.companyid = response.result.Employee;
constants.role = response.result.Role;
constants.name = response.result.FirstName;
console.log('test');
},
function(response){
console.log('error');
});
}
]);
So the problem is that the user information is set to late and angular already goes to my homeController where he uses the companyId that is not being set yet.
Thankyou
The problem in your current code is return $http.post are having two return statement in your validateUser method. Which is returning $http.get before returning return vm.deferred.promise; & that why customly created promise doesn't get returned from your method. Though by removing first return from $http.get will fix your problem, I'd not suggest to go for such fix, because it is considered as bad pattern to implement.
Rather I'd say, you should utilize promise return by $http method, & use .then to return data to chain promise mechanism.
Code
function authenticationservice($http, $location, authenticationFactory, $q, GLOBALS, $cookies) {
this.validateUser = function() {
var vm = this;
data = {
"api_token": api_token
};
return $http.post(GLOBALS.url + 'show/employee/' + $cookies.get('employeeid'), data)
.then(function(response) {
var data = response.data;
retrun data;
}, function(err) {
return $q.reject(err);
});
}
}
To make sure that $ http return a $ promise object you need to check that the action in the controller returns a value and it is not a void action.
I have done a lot of reading around this, but ultimately the tutorials and guides I have found differ too much for me to get a decent grasp on this concept.
This is what I want to achieve:
1) Simple http request from our server [Any API for demonstration]
2) Run a function with data from (1). [Remove a property from the object]
3) Use result and length of (2) to run a loop of $http requests to our server. [Or any server]
4) This will result in 6 different objects. Run a function on these 6 objects. [Add a property]
5) Once ALL of this is done, run a separate function [Log "finished"]
How can this be achieved using promises? How do I pass data from (1) via a promise to (2)? Is this the right way to achieve what I need to do?
If anyone can show me how this should be structured it would be immensely helpful; I have kept the functions as simple as possible for this question.
Yes, promises are very nice to structure solutions for this kind of problems.
Simplified solution (more or less pseudo-code):
$http(...)
.then(function(response) {
// do something with response, for example:
var list = reponse.data.list;
// return it so that you can use it in the next 'then'.
return list;
})
.then(function(list) {
var promises = [];
angular.forEach(list, function(item) {
// perform a request for each item
var promise = $http(...).then(function(itemResponse) {
itemResponse.extraProperty = true;
return itemResponse;
});
// we make an array of promises
promises.push(promise);
});
// combine all promises into one and return it for the next then()
return $q.all(promises);
})
.then(function(itemsList) {
// itemsList is now an array of all parsed item responses.
console.log(itemsList);
});
(Hopefully this is right, I did not tested it.)
As you can see, you can return values in a callback to pass it to the next then(), or you can pass a promise, and this will result in calling the next callback when it resolves. $q.all() is used to combine multiple promises into one and resolve if all are resolved.
Edit: I realised that you can optionally leave out these three lines:
return list;
})
.then(function(list) {
But it is nice syntax though, because the separation of tasks is more visible.
Check code below, it could contains syntax error, the important is the structure. Step3 contains multiple(6) $http requests, it waits until the last request response to return a unique response object (array) containing response for each $http requets.
//Step 1
var Step1 = function () {
$http.get('api/controller').success(function (resp) {
var object1 = resp;
Step2(object1);
Step3(object1).then(function (resp) {
//resp.data is an array containing the response of each $http request
Step4(resp);
Step5();
});
});
}
//Step2
var Step2 = function(obj){
//do whatever with the object
}
//Step3
var Step3 = function (object1) {
var call = $q.defer();
var get1 = $http.get(object1[0].url);
var get2 = $http.get(object[1].url2);
//...
var get6 = $http.get(object[5].url6);
$q.all([get1, get2,..get6]).then(function (resp) {
call.resolve(resp);
});
return call.promise;
}
//Step4
var Step4 = function (resp) {
for (var i=0; i<resp.data.lenght;i++){
DoWhatEver(resp.data[i]);
};
}
//Step5
var Step5 = function () {
alert("Finished");
}
Step1(); //Call Step1 function
Don't know why you have difficulty implementing this, but maybe $q.all() is what you're missing:
var config1={method:'GET',url:'/api/...'};
$http(config1).success(function(resultsFrom1){
functionForResultsOf1(resultsFrom1);
})
var functionForResultsOf1 = function(resultsOf1){
//remove something from the result, assuming this is a synchronous operation
resultsOf1.splice()...;
var promises=makePromises(*pass whatever you want*);
$q.all(promises).then(function(aggregateOfAllCallsToServer){
angular.forEach(aggregateOfAllCallsToServer,function(data){
//do something to data from each call to the server
})
console.log("finished");
})
}
var makePromises = function(serverUrls){
var promises = [];
angular.forEach(serverUrls, function(url) {
var promise=$http({
url : '/api/'+url,
method: 'GET',
})
promises.push(promise);
});
return $q.all(promises);
}
I am trying to read data from json and wait until data will be fetched into $scope.urls.content. So I write code:
$scope.urls = { content:null};
$http.get('mock/plane_urls.json').success(function(thisData) {
$scope.urls.content = thisData;
});
And now I am trying to write something like callback but that doesn't work. How can i do that? Or is there any function for this? I am running out of ideas ;/
Do you mean that ?
$http.get('mock/plane_urls.json').success(function(thisData) {
$scope.urls.content = thisData;
$scope.yourCallback();
});
$scope.yourCallback = function() {
// your code
};
You want to work with promises and $resource.
As $http itself returns a promise, all you got to do is to chain to its return. Simple as that:
var promise = $http.get('mock/plane_urls.json').then(function(thisData) {
$scope.urls.content = thisData;
return 'something';
});
// somewhere else in the code
promise.then(function(data) {
// receives the data returned from the http handler
console.log(data === "something");
});
I made a pretty simple fiddle here.
But if you need to constantly call this info, you should expose it through a service, so anyone can grab its result and process it. i.e.:
service('dataService', function($http) {
var requestPromise = $http.get('mock/plane_urls.json').then(function(d) {
return d.data;
});
this.getPlanesURL = function() {
return requestPromise;
};
});
// and anywhere in code where you need this info
dataService.getPlanesURL().then(function(planes) {
// do somehting with planes URL
$scope.urls.content = planes;
});
Just an important note. This service I mocked will cache and always return the same data. If what you need is to call this JSON many times, then you should go with $resource.