AngularJS Server Side Pagination - Recursively call API - javascript

I'm trying to call an API that takes parameters start and count, like this:
function handleSuccess() {
if (!!response.data) {
return (response.data);
} else {
return q.reject(response.data);
}
}
function handleError() {
// do some handling
}
function getData(url, sortBy) {
var count = 10;
var start = 1;
var request = http({
cache: true,
method: "GET",
url: url,
params: {
sortBy: sortBy,
sortOrder: "ASC",
count: count, // e.g. 10
start: start // e.g. 1
}
});
return (request.then(handleSuccess, handleError));
}
The JSON response from the API could contain a "next" link that would give me the URL to call to get the next set of data if there exists more...this is how the pagination works.
What's the best way to do this and concatenate all the data returned into one JSON response?

Assuming some part of the data response is an array, then simply use normal array concat() to combine it with previous pages of data in your handleSuccess() callback.

I've found that a service-oriented way is most useful when trying to get paged data from the same endpoint as it is easy to share services and objects between controllers and directives.
First, I would set up the service layer of your application so that all objects being requested have common, generic methods like so (I highly recommend you use ngResource or RESTAngular or something like that):
angular.module('myModule')
.factory('ApiObject', function($http, $q) {
ApiObject = function ApiObject(attributes) {
angular.extend(this, attributes);
};
ApiObject.query = function(url, parameters) {
var deferred = $q.defer();
$http.get(url, {params: parameters}).then(function(data) {
var results = [];
angular.forEach(data, function(apiObject) {
results.push(new ApiObject(apiObject));
});
deferred.resolve(results);
}, function(error) {
// Do error stuff
deferred.reject(error);
});
return deferred.promise;
};
return ApiObject;
});
Then set up a service to manage your paging data that accepts your generic services as well as parameters and configuration options. Also allow for events to be triggered within the service (see trigger and on methods) so that you know when new results are fetched. I've also written in a way for the results to be automatically concatenated onto the current result set:
angular.module('myModule')
.factory('SearchService', function() {
SearchService = function SearchService(service, params, config) {
this.searchParams = params || {};
this.config = config || {};
this.service = service;
this.results = [];
this.listeners = {};
};
SearchService.prototype.fetch = function(params) {
var _this = this;
this.service.query().then(function(results) {
if(_this.config.concatResults) {
_this.results = _this.results.concat(results);
// You probably should make sure results are unique at this point as that is a common problem with paging a changing API
} else {
_this.results = results;
}
_this.trigger('searchSuccess', _this.results);
});
};
SearchService.prototype.on = function(event, listener) {
(this.listeners[event] = (this.listeners[event] || [])).push(listener);
};
SearchService.prototype.trigger = function(event, payload) {
angular.forEach(this.listeners[event], function(listener) {
listener(payload);
});
};
SearchService.prototype.isLastPage = function() {
//logic here to determine last page
};
SearchService.prototype.nextPage = function() {
if(this.isLastPage()) {
return;
}
if(this.searchParams.page) {
this.searchParams.page++;
} else {
this.searchParams.page = 2;
}
this.fetch();
};
// Write more methods for previousPage, lastPage, firstPage, goToPage... etc.
return SearchService;
});
Then in your controller, you're going to want to instantiate the search service with some default parameters and configuration and then fetch the first page:
angular.module('myModule')
.controller('MyCtrl', function($scope, ApiObject, SearchService) {
$scope.searchService = new SearchService(ApiObject, {page: 1}, {concatResults: true});
$scope.searchService.on('searchSuccess', function(results) {
// Do something with results if you wish, but they'll already be stored in $scope.searchService
});
// Get the first page of data
$scope.searchService.fetch();
});
This is obviously a rough cut with a lot of room for improvement, but I hope this will be a good jumping off point to get you pointed in some sort of angular-style direction. In my experience, this is the best way to abstract out paging logic from the data/request layer in your services.

Related

How to avoid repeating http requests angularJS

Is there a way in Angular to avoid repeating http requests?
As you can see in the code above I'm making a call to retrieve the detailed info of a product.
The fact is that this call is associated to a button...
I would to avoid repetitive calls.
If I have clicked on the detailed-product-button obviously I don't need to make a call again to my service....the proper way will be to load the info once and then show and hided; but I don't know how to manage this on Angular.
(also I don't want to load the detail product from the scrach for very product, I want to loaded only on user's clic demand, but once)
$scope.seeInfo= function(id){
$http.get('/shop/getDetailInfo/'+id).
success(function(data, status) {
$scope.info = data.detailinfo;
if (data.detailinfo>0) $scope.showDetails=true;
else $scope.showDetails=false;
});
};
Angular $http has a cache functionality built in, might be all you need
https://docs.angularjs.org/api/ng/service/$http
$scope.seeInfo= function(id){
$http.get('/shop/getDetailInfo/'+id, {cache: true}).
success(function(data, status) {
$scope.info = data.detailinfo;
if (data.detailinfo>0) $scope.showDetails=true;
else $scope.showDetails=false;
});
};
update
I see you went for the "roll your own" solution instead, which generally is more bug prone than using what angular provides.
Here how to achieve the same:
// access the $http cache
var httpCache = $cacheFactory('$http');
// retrieve an element from cache
var detailInfo = httpCache.get('/shop/getDetailInfo/' + id);
// delete a cache element
httpCache.remove('/shop/getDetailInfo/' + id);
You can store every item that the user request in a factory and then check if the content is in the factory before do the ajax call.
$scope.seeInfo= function(id){
var detailinfo = someFactory.get(id); //get item data from factory if exist
if (angular.isUndefined(detailinfo) || detailinfo === null) {
$http.get('/shop/getDetailInfo/'+id).
success(function(data, status) {
someFactory.set(id, data); //set ajax data to factory
$scope.info = data.detailinfo;
if (data.detailinfo>0) $scope.showDetails=true;
else $scope.showDetails=false;
});
}
} else {
$scope.info = detailinfo;
if (detailinfo>0) $scope.showDetails=true;
else $scope.showDetails=false;
}
};
As well as someone said you can use the $http cache but i don't know how really it works
UPDATE
A someFactory example:
.factory('someFactory', [function() {
var storedItems = [];
return {
get: function(id) {
return storedItems[id];
},
set: function(id, data) {
storedItems[id] = data;
}
};
}]);
test the factory:
someFactory.set(12345, {"info":"Hello"});
someFactory.set(2, "World");
console.log(someFactory.get(12345).info); // returns Hello
console.log(someFactory.get(2)); //returns World
You can store strings, objects....
Hope it helps you
UPDATE2 FULL EXAMPLE CODE
var someApp = angular.module("someApp", [])
.controller('someCtrl', ['someFactory', function(someFactory) {
someFactory.set(12345, {"info":"Hello"});
someFactory.set(2, "World");
console.log(someFactory.get(12345).info); // returns Hello
console.log(someFactory.get(2)); //returns World
}]).factory('someFactory', [function() {
var storedItems = [];
return {
get: function(id) {
return storedItems[id];
},
set: function(id, data) {
storedItems[id] = data;
}
};
}]);
Bind first call with scope variable.
$scope.wasCalled = false;
$scope.seeInfo= function(id){
if ( $scope.wasCalled == false ) {
$http.get('/shop/getDetailInfo/'+id).
success(function(data, status) {
$scope.info = data.detailinfo;
$scope.wasCalled = true;
});
}
};
it's set on success so the server error code would be between 200 and 299.
Then you can set ng-show in view basing on $scope.wasCalled variable.
Here is implementation taking into account different id calls.
$scope.callIds = [];
$scope.wasCalled = function(id){
for ( var k = 0 ; k < $scope.callIds.length ; k++ )
if ( $scope.callIds[k] == id )
return true;
return false;
};
$scope.addCalled = function(id){
$scope.callIds.push(id);
};
$scope.seeInfo= function(id){
if ( $scope.wasCalled(id) == false ) {
$http.get('/shop/getDetailInfo/'+id).
success(function(data, status) {
$scope.info = data.detailinfo;
$scope.addCalled(id);
});
}
};
Checking if specified id was called, if not, call with $http and add id to list.

Waiting for function to complete in Angular JS

I have the following situation: When my app first runs, in the .run function, I issue an http request to get a list of "monitors". Once received, it populates a monitors array in a service. This service is used by many controllers to share data. This service is called ZMDataModel. ZMDataModel offers a function called isMonitorsLoaded(). When this returns 1, I know the monitors array is populated (and that the http call is complete)
Now, I have a page called Monitors, the controller for which is zmApp.MonitorCtrl, shown below. What I need to do, in this MonitorCtrl is to basically, right at the start, do an equivalent of:
while (ZMData.isMonitorsLoaded()!=1);
Now I obviously can't do that because it locks my browser up, and the browser never gets a chance to set isMonitorLoaded to 1 in the first place, so it becomes an endless loop.
I understand I need to put in a timeout somehow, but can't quite follow what I need to do in the controller. My controller code is below:
angular.module('zmApp.controllers').controller('zmApp.MonitorCtrl', function($scope, $http, ZMHttpFactory, ZMDataModel) {
$scope.monitors = [];
console.log("***Waiting for Monitors to load before I proceed");
// I can't do a tight loop waiting for ZMDataModel.isMonitorsLoaded
// so some timeout?
$scope.monitors = ZMDataModel.getMonitors();
console.log("I GOT " + $scope.monitors);
$scope.doRefresh = function() {
console.log("***Pull to Refresh");
$scope.monitors = [];
ZMHttpFactory.getMonitors().then(function(data) {
$scope.monitors = data;
$scope.$broadcast('scroll.refreshComplete');
console.log("I GOT " + $scope.monitors);
});
};
});
You can use $rootScope.$emit('eventName') which works like a broadcast of events for anyone who is subscribe to them whit $rootScope.$on('eventName'):
// In your monitor loaded method:
onload: function(){
//Here you can pass optional information to the listeners
// for Example an array of monitor or an object
$rootScope.$emit('MONITORS_LOADED',{
monitors: getMonitors()
});
}
// In your controller:
angular.module('zmApp.controllers').controller('zmApp.MonitorCtrl', function($rootScope, $scope, $http, ZMHttpFactory, ZMDataModel) {
$scope.monitors = [];
$rootScope.$on('MONITOR_LOADED', function(event, data) {
$scope.monitors = data;
// or
// $scope.monitors = ZMDataModel.getMonitors();
console.log("I GOT " + $scope.monitors);
}
$scope.doRefresh = function() {
//...
});
};
});
Why not using a promise which will resolve when your monitor servers is loaded? You can set up your service as:
angular.module('myApp')
.service ('ZMDataModel', ['$http', function ($http) {
function MyServices () {
var _deferred;
var _isLoading;
var me = this;
this.isLoaded = false;
this.load = function (reload) {
if (!_deferred || (!_isLoading && reload)) {
this.isLoaded = false;
_deferred = $q.defer();
_isLoading = true;
// make your call
$http ({get : 'http://your-site.com'})
.then (
function success (rawData) {
me.isLoaded = true;
// parse your data
me.monitors = rawData;
_deferred.resolve(me);
},
function fail (error) {
_deferred.reject(error);
_deferred = null;
if (onFail) {
me.monitors = [];
}
}
)
.finally (
function () {
_isLoading = false;
}
);
}
return _deferred.promise;
};
}
return MyServices;
}
]);
Now you can use your service everywhere like this:
angular.module('zmApp.controllers').controller('zmApp.MonitorCtrl', ['$scope', 'ZMDataModel', function($scope, ZMDataModel) {
$scope.monitors = [];
console.log("***Waiting for Monitors to load before I proceed");
// I can't do a tight loop waiting for ZMDataModel.isMonitorsLoaded
// so some timeout?
ZMDataModel.load ().then (
function (response) {
$scope.monitors = ZMDataModel.monitors;
}
);
$scope.doRefresh = function() {
console.log("***Pull to Refresh");
$scope.monitors = [];
ZMDataModel.load (true).then (
function (response) {
$scope.monitors = ZMDataModel.monitors;
}
);
};
}]);
It doesn't matter if an other controller loads the service first. As long as you use the load function without the 'refresh' flag set to true, the service won't load again

How to implement asynchronous computed observable with multiple $.ajax calls?

I'm attempting to implement an asynchronous computed observable as show here.
I can do it successfully for one ajax call. The challenge I have at the moment is how to perform various ajax calls in a loop building an array asynchronously and then returning the array to my computed observable array using jQuery promises.
Basically the HTML form works in the following way:
This a student course form.
For each row, users type the person number and on another column they'll type a list of course ids separated by commas. Eg 100, 200, 300.
The purpose of the computed observable is to store an array
containing course details for the courses entered in step 2.
The details are obtained by firing ajax calls for each course and storing HTTP response in the array.
I don't want users to wait for the result, thus the reason to implement an async computed observable.
My problem: I'm having problem returning the value of the final array to the observable. It's always undefined. The ajax calls work fine but perhaps I'm still not handling the promises correctly.
Here's the code for my class:
function asyncComputed(evaluator, owner) {
var result = ko.observable(), currentDeferred;
result.inProgress = ko.observable(false); // Track whether we're waiting for a result
ko.computed(function () {
// Abort any in-flight evaluation to ensure we only notify with the latest value
if (currentDeferred) { currentDeferred.reject(); }
var evaluatorResult = evaluator.call(owner);
// Cope with both asynchronous and synchronous values
if (evaluatorResult && (typeof evaluatorResult.done == "function")) { // Async
result.inProgress(true);
currentDeferred = $.Deferred().done(function (data) {
result.inProgress(false);
result(data);
});
evaluatorResult.done(currentDeferred.resolve);
} else // Sync
result(evaluatorResult);
});
return result;
}
function personDetails(id, personNumber, courseIds) {
var self = this;
self.id = ko.observable(id);
self.personNumber = ko.observable(personNumber);
self.courseIds = ko.observable(courseIds);
// Computed property to extract PIC details for additional PICs.
// This is computed observable which returns response asynchronously
self.courseDetails = asyncComputed(function () {
var courseIdsArray = self.courseIds().split(",");
var arr = [];
var arr_promises = [];
function getCourseDetails(courseId) {
var dfrd = $.Deferred();
var content = {};
content.searchString = courseId;
var url = 'MyURL';
return $.ajax(url, {
type: 'POST',
dataType: 'json',
data: requestData, // content of requestData is irrelevant. The ajax call works fine.
processdata: true,
cache: false,
async: true,
contentType: "application/json"
}).done(function (data) {
arr.push(new PicDetails(data.GenericIdentifierSearchResult[0]));
}).fail(function () {
alert("Could not retrieve PIC details");
}).then(function () {
dfrd.resolve();
});
}
if (courseIdsArray.length > 0) {
$.each(courseIdsArray, function (index, courseId) {
if (courseId.length > 0) {
arr_promises.push(getCourseDetails(courseId));
}
});
};
$.when.apply($, arr_promises).done(function () {
return arr;
})
}, this);
}
I think you dont really need a separate api/code for this.
You could just create observables for every input/value that changes on your site, and create a computed observable based on those.
e.g in rough pseudo code
self.id = ko.observable(id);
self.personNumber = ko.observable(personNumber);
self.courseIds = ko.observable(courseIds);
self.courseDetailsArray = ko.observableArray([]);
self.courseDetails = ko.computed(function() {
//computed the course details based on other observables
//whenever user types in more course ids, start loading them
$.get( yoururl, {self.courseIds and self.id}).success(data) {
when finished async loading, parse the data and push the new course details into final array
self.courseDetailsArray.push( your loaded and parsed data );
//since courseDetailsArray is observableArray, you can have further computed observables using and re-formatting it.
}
});
I have something a bit different from your approach, but you can build something like an asyncComputed out of it if you prefer:
make a simple observable that will hold the result
make a dictionary of promises that you'll basically keep in sync with the array of course ids
when the array of course ids change, add / remove from the dictionary of promises
wrap all your promises in a when (like you're doing) and set the result when they're all done
Basic idea:
var results = ko.observable([]);
var loadingPromises = {};
var watcher = ko.computed(function () {
var ids = ko.unwrap(listOfIds);
if (ids && ids.length) {
ids.forEach(function (id) {
if (!loadingPromises.hasOwnProperty(id)) {
loadingPromises[id] = $.get(url, {...id...});
}
});
var stillApplicablePromises = {};
var promises = []; // we could delete from loadingPromises but v8 optimizes delete poorly
Object.getOwnPropertyNames(loadingPromises).forEach(function (id) {
if (ids.indexOf(id) >= 0) {
stillApplicablePromises[id] = loadingPromises[id];
promises.push(loadingPromises[id]);
}
});
loadingPromises = stillApplicablePromises;
$.when.apply(this, promises).then(function () {
// process arguments here however you like, they're the responses to your promises
results(arguments);
});
} else {
loadingPromises = {};
results([]);
}
}, this);
This is the file (that may change) where you can see this "in real life": https://github.com/wikimedia/analytics-dashiki/blob/master/src/components/wikimetrics-visualizer/wikimetrics-visualizer.js
And the basic fiddle: http://jsfiddle.net/xtsekb20/1/

Calling only once / caching the data from a $http get in an AngularJS service

This may sound like a really simply/stupid question but I need to ask it as I haven't came across this scenario before... okay I have a service in my angularJS app. this service currently contains 4 methods that all perform 80% the same functionality/code and I wish to make this more efficient. Here is what my service looks like (with a lot of code removed):
.factory('townDataService', function ($http) {
var townList = {};
townList.getTownList = function () {
return $http({method: 'GET', url: '/api/country/cities'})
.then(function (response) {
// HERE WE FORMAT THE response as desired... that creates a returnArray
var returnArray = [];
// loop through the countries
var JsonData = response.data;
for (key in JsonData['countries']) {
// formatting code...
}
// end of repeated CODE
return returnArray; // this is array, we don't do any formatting here
});
};
townList.getCurrentTown = function (place) {
return $http({method: 'GET', url: '/api/country/cities'})
.then(function (response) {
// HERE WE FORMAT THE response as desired... that creates a returnArray
var returnArray = [];
// loop through the countries
var JsonData = response.data;
for (key in JsonData['countries']) {
// formatting code...
}
// end of repeated code
// now the format further / work with the returnArray...
for (var i = 0; i < returnArray.length; i++) {
// do stuff
}
return currentTown; // this is a string
});
};
townList.getCurrentCountry = function (place) {
return $http({method: 'GET', url: '/api/country/cities'})
.then(function (response) {
// HERE WE FORMAT THE response as desired... that creates a returnArray
var returnArray = [];
// loop through the countries
var JsonData = response.data;
for (key in JsonData['countries']) {
// formatting code...
}
// end of repeated code
// now the format further / work with the returnArray...
for (var i = 0; i < returnArray.length; i++) {
// do stuff
}
return currentCountry; // this is a string
});
};
return townList;
}
)
;
Now I repeat the same $http 'GET' in each method and the same formatting code (which is a lot of nested loops) before returning a object array or a string. This is far from efficent! What is the best way to put this functionality into it's own function so we only call the GET url once but still return a promise with each method? Should I set the results of the $http({method: 'GET', url: '/api/country/cities'}) as a var and inject / pass it into each method before formatting the data if necessary? Should I use some sort of $cacheFactory?
Sorry if this is a dumb question and if I haven't explained myself well I shall rephrase the questions.
Thanks in advance.
It is just as you say; this code can (and should) be refactored in many ways. One example:
Let us factor the HTTP stuff into a separate service, that will also take care of caching. (Another idea for this would be to have a service for the HTTP/remote calls and another - maybe a general use decorator - to handle caching. LEt us not go into so much detail for now.) And let us put the formatting code in another method:
The remote call service:
.service('townHttpService', function($http, $q) {
var cache;
function getCities() {
var d = $q.defer();
if( cache ) {
d.resolve(cache);
}
else {
$http({method: 'GET', url: '/api/country/cities'}).then(
function success(response) {
cache = response.data;
d.resolve(cache);
},
function failure(reason) {
d.reject(reason);
}
});
}
return d.promise;
}
function clearCache() {
cache = null;
}
return {
getCities: getCities,
clearCache: clearCache
};
})
The formatter:
.service('townFormatter', function() {
return function townFormatter(jsonData) {
// HERE WE FORMAT THE response as desired... that creates a returnArray
var returnArray = [], key;
// loop through the countries
for (key in jsonData['countries']) {
// formatting code...
}
// end of repeated CODE
return returnArray; // this is array, we don't do any formatting here
};
})
Your townDataService, written in terms of the above:
.factory('townDataService', function (townHttpService, townFormatter) {
var townList = {};
townList.getTownList = function () {
return townHttpService.getCities().then(townFormatter);
};
townList.getCurrentTown = function (place) {
return townHttpService.getCities().then(townFormatter).then(function(cityList) {
var currentTown;
for (var i = 0; i < cityList.length; i++) {
// do stuff
}
return currentTown; // this is a string
});
};
townList.getCurrentCountry = function (place) {
return townHttpService.getCities().then(townFormatter).then(function(cityList) {
var currentCountry;
for (var i = 0; i < cityList.length; i++) {
// do stuff
}
return currentCountry; // this is a string
});
return townList;
})
I guess you got two questions there removing repeated logic and best way to cache results.
First - Removing duplicate code:
Looks like townList.getTownList is the common method, the other two methods is an extension of this method.
So,
townList.getCurrentTown = function(place) {
var towns = townList.getTownList();
for (var i = 0; i < returnArray.length; i++) { //additional stuff
}
return currentTown;
};
townList.getCurrentCountry = function(place) {
var towns = townList.getTownList();
for (var i = 0; i < returnArray.length; i++) { //additional stuff
}
return currentCountry;
};
Second - caching values
Now the call is being http made only in townList.getTownList, the logic to cache can be easily implemented here. But this depends on whether the data will be onetime fetch or can be refreshed.
One time fetch: just enable the cache in the http call $http({method: 'GET', url: '/api/country/cities', cache:true});
Refresh based on request: I would pass an refresh variable to inform whether data has to be refreshed or not. So if the refresh is true or the townList is empty the data will be fetched.
var srvc = this;
var townList;
townList.getTownList = function(refresh ) {
if (refresh || !townList) {
srvc.townList = $http({
method: 'GET',
url: '/api/country/cities'
})
.then(function(response) {
var returnArray = [];
var JsonData = response.data;
for (var key in JsonData.countries) {}
return returnArray; // this is array, we don't do any formatting here
});
}
return townList;
};
There is nothing special that you can do and receive some considerable benefit. You would definitely need to cache your GET response and refactor a bit to avoid code duplication and improve readability:
.factory('townDataService', function ($http) {
var getCitiesAsync = function(){
return $http({method: 'GET', url: '/api/country/cities', cache:true});
};
var townList = {};
townList.getTownList = function () {
return getCitiesAsync().then(prepareTownList);
};
var prepareTownList = function(response){
//extract towns and do whatever you need
return result;
};
...
As for using $cacheFactory - seems like an overhead for such a simple scenario, just use built-in cache option.
To avoid timing issues, it is perhaps good to extend the solution a little bit:
function getCities() {
var d = $q.defer();
if( cache ) {
d.resolve(cache);
}
else {
$http({method: 'GET', url: '/api/country/cities'}).then(
function success(response) {
if (!cache) {
cache = response.data;
}
d.resolve(cache);
},
function failure(reason) {
d.reject(reason);
}
});
}
return d.promise;
}
After a (perhaps second or third) call to the webservice succeeds, one checks if the cache variable was set while waiting for server response. If so, we can return the already assigned value. That way, there will be no new assignment to the variable cache if multiple calls were issued:
function success(response) {
if (!cache) {
cache = response.data;
It does not have to make problems but if you rely on having identical objects (for example when working with data binding) it is great to be sure to only fetch data once!

Caching an array in an angularjs Service

I'm new to AngularJS and am still trying to wrap my head around using services to pull data into my application.
I am looking for a way to cache the result of a $http.get() which will be a JSON array. In this case, it is a static list of events:
[{ id: 1, name: "First Event"}, { id: 2, name: "Second Event"},...]
I have a service that I am trying to use to cache these results:
appServices.service("eventListService", function($http) {
var eventListCache;
this.get = function (ignoreCache) {
if (ignoreCache || !eventListCache) {
eventListCache = $http.get("/events.json", {cache: true});
}
return eventListCache;
}
});
Now from what I can understand I am returning a "promise" from the $http.get function, which in my controller I add in a success callback:
appControllers.controller("EventListCtrl", ["$scope", "eventListService",
function ($scope, eventListService) {
eventListService.get().success(function (data) { $scope.events = data; });
}
]);
This is working fine for me. What I'd like to do is add an event to the eventListService to pull out a specific event object from eventListCache.
appServices.service("eventListService", function($http) {
var eventListCache;
this.get = function (ignoreCache) { ... }
//added
this.getEvent = function (id) {
//TODO: add some sort of call to this.get() in order to make sure the
//eventListCache is there... stumped
}
});
I do not know if this is the best way to approach caching or if this is a stupid thing to do, but I am trying to get a single object from an array that may or may not be cached. OR maybe I'm supposed to call the original event and pull the object out of the resulting array in the controller.
You're on the right track. Services in Angularjs are singeltons, so using it to cache your $http request is fine. If you want to expose several functions in your service I would do something like this. I used the $q promise/deferred service implementation in Angularjs to handle the asynchronus http request.
appServices.service("eventListService", function($http, $q) {
var eventListCache;
var get = function (callback) {
$http({method: "GET", url: "/events.json"}).
success(function(data, status) {
eventListCache = data;
return callback(eventListCache);
}).
}
}
return {
getEventList : function(callback) {
if(eventListCache.length > 0) {
return callback(eventListCache);
} else {
var deferred = $q.defer();
get(function(data) {
deferred.resolve(data);
}
deferred.promise.then(function(res) {
return callback(res);
});
}
},
getSpecificEvent: function(id, callback) {
// Same as in getEventList(), but with a filter or sorting of the array
// ...
// return callback(....);
}
}
});
Now, in your controller, all you have to do is this;
appControllers.controller("EventListCtrl", ["$scope", "eventListService",
function ($scope, eventListService) {
// First time your controller runs, it will send http-request, second time it
// will use the cached variable
eventListService.getEventList(function(eventlist) {
$scope.myEventList = eventlist;
});
eventListService.getSpecificEvent($scope.someEventID, function(event) {
// This one is cached, and fetched from local variable in service
$scope.mySpecificEvent = event;
});
}
]);
You are on the right track. Here's a little help:
appServices.service("eventListService", function($http, $q) {
var eventListCache = [];
function getList(forceReload) {
var defObj = $q.defer(), listHolder;
if (eventListCache.length || forceReload) {
listHolder= $http.get("/events.json", {cache: true});
listHolder.then(function(data){
eventListCache = data;
defObj.resolve(eventListCache);
});
} else {
defObj.resolve(eventListCache);
}
return defObj.promise;
}
function getDetails(eventId){
var defObj = $q.defer();
if(eventId === undefined){
throw new Error('Event Id is Required.');
}
if(eventListCache.length === 0){
defObj.reject('No Events Loaded.');
} else {
defObj.resolve(eventListCache[eventId]);
}
return defObj.promise;
}
return {
eventList:getList,
eventDetails:getDetails
};
});
Then, in your controller, you handle it like this:
appControllers.controller("EventListCtrl", ["$scope", "eventListService",
function ($scope, eventListService) {
var eventList = eventListService.getList();
eventList.then(function(data){
$scope.events = data;
});
$scope.getEventsList = function(reloadList){
eventList = eventListService.getList(reloadList);
eventList.then(function(data){
$scope.events = data;
});
};
$scope.getEventDetails = function(eventID){
var detailsPromise = eventListService.getDetails(eventID);
detailsPromise.then(function(data){
$scope.eventDetails = data;
}, function(reason){
window.alert(reason);
});
}
}
]);
This way, your events are loaded when the controller first loads, and then you have the option to request a new list by simply passing in a boolean. Getting event details is also handled by an internal promise to give you some error handling without throwing a disruptive error.

Categories