The Setting: I want to have a service that multiple controllers can query for data pulled using $http. The initial solution was to use promises as suggested here.
The Problem: Each time a controller queries the service, the service then returns an $http promise, resulting in multiple queries that just pulls the same data from a remote server, over and over again.
A Solution: The service function returns either data or a promise like below. And it is up to the controller to check and act accordingly.
app.factory('myService', function($http) {
var items = [];
var myService = {
getItems: function() {
// if items has content, return items; otherwise, return promise.
if (items.length > 0) {
return items;
} else {
var promise = $http.get('test.json').then(function (response) {
// fill up items with result, so next query just returns items.
for(var i=0;i<response.data.length;i++){
items.push(response.data[i]);
}
return items;
});
// Return the promise to the controller
return promise;
}
};
return myService;
});
So when a controller needs that data, the controller just does something like this:
app.controller('MainCtrl', function( myService,$scope) {
var promiseOrData = myService.async();
// Check whether result is a promise or data.
if ( typeof promiseOrData.then === 'function'){
// It's a promise. Use then().
promiseOrData.then( function(data ){
$scope.data = data;
});
} else {
// It's data.
$scope.data = data;
}
});
So the question is: Is there a better way of doing this? With many controllers, this method would have a lot of duplicate code. Ideally, the controllers will just query the service for data directly.
Thanks!
$http returns a promise, we can use that instead of creating a new one with $q. Once the promise is resolved, we can keep returning it.
.factory('myService', ['$http','$q', function($http, $q) {
var items = [];
var last_request_failed = true;
var promise = undefined;
return {
getItems: function() {
if(!promise || last_request_failed) {
promise = $http.get('test.json').then(
function(response) {
last_request_failed = false;
items = response.data;
return items;
},function(response) { // error
last_request_failed = true;
return $q.reject(response);
});
}
return promise;
},
};
}])
In your controller:
myService.getItems().then(
function(data) { $scope.data = data; }
);
Create your own promise that resolves to either the cached data or the fetched data.
app.factory('myService', function($http, $q) {
var items = [];
var myService = {
getItems: function() {
var deferred = $q.defer();
if (items.length > 0) {
//resolve the promise with the cached items
deferred.resolve(items);
} else {
$http.get('test.json').then(function (response) {
// fill up items with result, so next query just returns items.
for(var i=0;i<response.data.length;i++){
items.push(response.data[i]);
}
//resolve the promise with the items retrieved
deferred.resolve(items);
},function(response){
//something went wrong reject the promise with a message
deferred.reject("Could not retrieve data!");
});
}
// Return the promise to the controller
return deferred.promise;
};
return myService;
});
Then consume the promise in your controller.
app.controller('MainCtrl', function( myService,$scope) {
var promiseOrData = myService.getItems();
promiseOrData.then( function(data){
$scope.data = data;
},
function(data){
// should log "Could not retrieve data!"
console.log(data)
});
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With