Reputation: 9722
I'm trying to build a service or factory that allows me to load date through some API calls. Most of this data will need to be re-used, so essentially I only want to make one API call, the next time I need that data, it should just return it.
Now whenever I make an API call, and before it's finished I make the same call, I want the second call to wait until the first one is completed.
Essentially when I do this:
dataService.getMenu() // Make API call
dataService.getMenu() // Wait for the first API call to be completed and return that data
// Somewhere else
dataService.getMenu() // Return data as API call was already made
My factory looks like this:
(function() {
var app = angular.module('dataService', []);
app.factory('dataService', ['$http', '$q', function($http, $q) {
var links = [],
jobs = [];
return {
getMenu: function() {
var deferred = $q.defer();
console.log(links);
if(links.length > 0) {
deferred.resolve(links);
} else {
$http.get('../server/api.php?ajax=true&action=getCats').success(function(data) {
links = data;
deferred.resolve(data);
})
}
return deferred.promise;
}
}
}])
})();
Upvotes: 1
Views: 994
Reputation: 3148
I know I am bit late to answer this question, but I faced the same problem and came up with a solution after lot of research.
The approach to achieve the above requirement is to use queuing of calls.
The following are the steps for the same:
defer.promise
for each call. IsDataPresent = true
(initially it was false).IsDataPresent=== true
, if true then resolve the next call's promise with the data of local variable.See the code below for the same:
app.factory('dataService', ['$http', '$q', function($http, $q) {
var links = '',
jobs = [],
isDataAlreadyPresent = false;
var getMenuCall = function() {
var call = jobs[0]; // Retrieve first promise from the queue
if (isDataAlreadyPresent) { // This will be false for first call and true for all other
call.defer.resolve(links);
} else {
$http.get('../server/api.php?ajax=true&action=getCats').success(function(data) { //Get API data
isDataAlreadyPresent = true; //Set parameter to true
links = data; // Set local variable to received data
call.defer.resolve.resolve(data); // Resolve the first promise
jobs.shift(); // Remove first item from the queue
if (jobs.length > 0) {
getMenuCall(); // Execute the function for next call's promise in the queue. This time isDataAlreadyPresent== true will be true so it's promise will be resolved by the links data thus avoiding extra call.
}
});
}
return deferredMenu.promise;
};
return {
getMenu: function() {
var defer = $q.defer(); // Create promise for the call
jobs.push({
defer: defer // Push each call's promise to the queue
});
if (jobs.length === 1) { // For the first call make call above function which will make API call
getMenuCall();
}
return defer.promise; // Defer promise for the time being
}
}
}]);
Upvotes: 0
Reputation: 30108
You can make use of the cache
config in the $http
service to do this for you. As stated in the $http
caching documentation:
To enable caching, set the request configuration cache property to true (to use default cache) or to a custom cache object (built with $cacheFactory). When the cache is enabled, $http stores the response from the server in the specified cache. The next time the same request is made, the response is served from the cache without sending a request to the server
Note that even if the response is served from cache, delivery of the data is asynchronous in the same way that real requests are.
If there are multiple GET requests for the same URL that should be cached using the same cache, but the cache is not populated yet, only one request to the server will be made and the remaining requests will be fulfilled using the response from the first request.
The statement above fits the requirements stated in your question. Furthermore, I have omitted the $q
service since $http
methods already provide promises that you need, you simply need to have the data object in your response using the then()
$q
service method.
(function() {
var app = angular.module('dataService', []);
app.factory('dataService', ['$http', function($http) {
return {
getMenu: function() {
return $http.get('../server/api.php?ajax=true&action=getCats', {cache: true})
.then(function(response) {
return response.data;
});
}
};
}])
})();
Upvotes: 0
Reputation: 42669
Just move the declaration for defer outside the getMenu function, into factory
app.factory('dataService', ['$http', '$q', function($http, $q) {
var links = [],
jobs = [],
deferredMenu = $q.defer();
Now use deferredMenu promise in side your getMenu call.
getMenu: function() {
if(links.length > 0) {
deferredMenu.resolve(links);
} else {
$http.get('../server/api.php?ajax=true&action=getCats').success(function(data) {
links = data;
deferredMenu.resolve(data);
})
}
return deferredMenu.promise;
}
Upvotes: 2