Reputation: 3538
I have a service that is called by multiple controllers. It loads data into an object categories
:
.service('DataService', ['$http', '$q', function($http, $q) {
var categories = {};
// Return public API.
return({
setCategory: setCategory,
getCategory: getCategory,
getJSON: getJSON,
categories: categories
});
function setCategory(name, category) {
console.log("setting category");
console.log(name, category)
categories[name] = category;
}
function getCategory(name) {
console.log("getCategories:");
console.log(categories[name]);
return categories[name];
}
function getJSON() {
//JSON stuff, where categories gets its initial values.
}
I call getCategory(name) in many places, and in some instances, it is called before categories
has populated, e.g:
$scope.category = DataService.getCategory(name);
//$scope.category is undefined
How can I build this Service so that getCategories waits until categories is defined before returning its value? Alternately, how can I write the Controller so that getCategories isn't defined until categories has a value? I have tried using a $scope.$watch function in the controller to watch DataService.categories, to no success- it never logs an updated value.
Upvotes: 2
Views: 2042
Reputation: 367
Inside DataService
yourApp.service('DataService', function($resource, $q) {
var resource = $resource('/api/category/:id', {}, {
query: {
method: 'GET',
isArray: false,
cache: false
},
save: {
method: 'POST',
isArray: false,
cache: false
}
});
return {
getCategory: function(id) {
var deferred = $q.defer();
resource.query({id: id},
function(response) {
deferred.resolve(response);
},
function(response) {
deferred.reject(response);
}
);
return deferred.promise;
},
setCategory: function(categoryObj) {
var deferred = $q.defer();
resource.save(categoryObj,
function(response) {
deferred.resolve(response);
},
function(response) {
deferred.reject(response);
}
);
return deferred.promise;
},
getJSON: function() {
// stuff to do
}
};
});
Inside DataController:
yourApp.controller('DataCtrl', function($scope, DataService) {
$scope.handleSuccessResponse = function(response) {
$scope.data = response;
};
$scope.handleErrorResponse = function(response) {
$scope.error = response;
};
DataService.getCategory(123).then($scope.handleSuccessResponse, $scope.handleErrorResponse);
});
Upvotes: 0
Reputation: 4623
Use the promises you're already injecting in your service. Here is just one of the many possible ways you can do this:
var pendingQueue = [];
var loaded = false;
var self = this;
function getCategory(name) {
var deferred = $q.defer();
if (loaded) {
// Resolve immediately
console.log('Already loaded categories, resolving immediately...');
deferred.resolve(self.categories[name]);
return deferred.promise;
}
// Queue the request
pendingQueue.push({
promise: deferred.promise,
name: name
});
if (pendingQueue.length === 1) {
console.log('First request for a category, requesting...
// We are the FIRST request. Call whatever it takes to load the data.
// In a 'real' language this wouldn't be thread-safe, but we only have one, so...
$http.get('/my-data').then(function(data) {
self.categories = data;
console.log('Loaded categories', self.categories);
loaded = true;
pendingQueue.map(function(entry) {
entry.promise.resolve(entry.name);
});
pendingQueue.length = 0;
});
}
return deferred.promise;
}
Then in your controller:
DataService.getCategory(name).then(function(category) {
// Do something with category here
});
This will:
For the first request, make the async request and then resolve the promise once the data is obtained.
For the second - Nth request BEFORE the data is obtained, queue those without making duplicate requests.
For requests AFTER the data is obtained, resolve immediately with the requested data.
No error handling is done - you should use deferred.reject() to send those back to the caller, and .catch() / .finally() to handle them in the controller(s).
There are many solutions - this is just one option.
Upvotes: 1