Javascript: Schedule HTTP requests for later - javascript

I'm developing an app using Phonegap (AngularJS + HTML5) and I want some of the functionality to be available even when the user is offline.
What I was thinking of doing is when I need to do an HTTP request, check if the device is online. If it is, then make the request. Otherwise, store the request in an array of other requests, and when you go back online start processing all these requests one-by-one.
So, I have created a service called HttpRequestScheduler which looks something like the following:
.service('HttpRequestSchedule', ['$http', function ($http) {
var requests = [];
// schedule function tries to send the request
// if it doesn't succeed, it adds it to the requests queue
function schedule(httpRequest) {
$http(
httpRequest
).success(function () {
}).error(function () {
requests.push(httpRequest);
});
}
// this method is called every time the device goes back online
function processRequests() {
while (requests.length !== 0) {
var currentRequest = requests.splice(0, 1)[0];
$http(
currentRequest
).success(function () {
}).error(function () {
requests.push(currentRequest);
});
}
}
return {
schedule: schedule,
processRequests: processRequests
};
}])
I guess this is a requirement for many apps out there. So my question is, is this the proper/usual way of doing such a thing? Or is there a better way?

To check if your device is online or not you should use cordova-plugin-network-information. Also when you're using Angular inside a Cordova-like application, ngCordova will help you a lot !
To answer your question, this a classic problem and the best is to find your own logic mixing an $interval watching regularly the connection via network-information plugin when it's down. When the network connection goes back, you have to execute the list of tasks that you stored in your LocalStorage.

Related

How do I prefetch url's in ionic/angularjs?

I am pretty new to ionic 1 and I am working on an application (with Ionic 1 and angular js) with multiple URLs where each URL brings up a list of categories, followed by a list of items for each category and each item has a document URL. How do I preload all these URLs on launch in the background but not display them?Is there any way this can be achieved? a good code sample or tutorial will help greatly.
Also, please let me know if this will be the best approach, as in pre-loading and pre-caching all content upon launch or should it be done category by category or some other way.
Thanks in advance!
You can make multiple Asynchronous service calls in background using $q.
Make a list of URL's in an array and call them at once using $q.all(listOfURL).
Using promises retrieve each response.
By making this asynchronous you can save lot of time.
After getting response you can either store them in $rootScope or in localStorage/sessionStorage.
Update - As the OP is already aware of and using localStorage, thus additional suggestions :-
In that case, you could either call all of your service methods for fetching data at startup or you could use a headless browser such as 'PhantomJS' to visit these URLs at startup and fetch the data.
Thus, your code would look something like :-
var webPage = require('webpage');
var page = webPage.create();
page.open('http://www.google.com/', function(status) {
console.log('Status: ' + status);
// Do other things here...
});
For more information, regarding PhantomJS, please refer to the following links :-
http://phantomjs.org/
http://phantomjs.org/api/webpage/method/open.html
Earlier Suggestions
Make an HTTP request in your service to fetch the data and store it to localStorage, as is shown below :-
$http.get('url', function(response) {
var obj = response.data;
window.localStorage.setItem('key', JSON.stringify(obj)); // Store data to localStorage for later use
});
For fetching data :-
var cachedData = JSON.parse(window.localStorage.getItem('key')); // Load cached data stored earlier
Please refer to the following link for detailed information regarding 'localStorage' :-
https://www.w3schools.com/html/html5_webstorage.asp
Hope this helps!
Best way to share data between different views in angular is to use a service as it is a singleton and can be used in other controllers.
In your main controller you can prefetch your lists of categories asynchronously through a service which can be shared for next views.Below is a small demo which you refer
angular.module("test").service("testservice",function('$http',$q){
var lists = undefined;
// fetch all lists in deferred technique
this.getLists = function() {
// if lists object is not defined then start the new process for fetch it
if (!lists) {
// create deferred object using $q
var deferred = $q.defer();
// get lists form backend
$http.get(URL)
.then(function(result) {
// save fetched posts to the local variable
lists = result.data;
// resolve the deferred
deferred.resolve(lists);
}, function(error) {
//handle error
deferred.reject(error);
});
// set the posts object to be a promise until result comeback
lists = deferred.promise;
}
// in any way wrap the lists object with $q.when which means:
// local posts object could be:
// a promise
// a real lists data
// both cases will be handled as promise because $q.when on real data will resolve it immediately
return $q.when(lists);
};
this.getLists2=function(){
//do it similarly as above
};
}).controller("mainController",function(testservice,$scope){
$scope.lists1=testervice.getLists()
.then(function(lists) {
//do something
});
};
$scope.lists2=testervice.getLists2()
.then(function(lists) {
//do something
});
};
$scope.lists1();
$scope.lists2();
}).controller("demoController1",function(testservice,$scope){
$scope.lists1=testervice.getLists()
.then(function(lists) {
//do something
});
};
$scope.lists2=testervice.getLists2()
.then(function(lists) {
//do something
});
};
$scope.lists1();
$scope.lists2();
});
I am assuming you don't want to load data in next screens, deliver user flawless experience.
Yes you can start loading URLs on you very first page as you want them to get the data you want to use in future screens.
In terms of storage
In AngularJs if you want something to persist throughout the application scope you should use $rootscope[beware keeping lot of data
may leads to memory issues, you need to clear it regularly].
Or another option is to store it in Localstorage. And fetch as per your need.
If you want you can share those arrays between different controllers of screens.
While loading[response getting from server] you can do two things
1. get single JSON response having all the data
2.have multiple urls, and load them serially.
As per your requirement of loading 5th (page)screen data in advance it's not good practice, and even stop user from seeing updates but as it's your requirement. We've couple of approaches:
Add all the category and their respective details as per your pastebin like cardiac then it's details.. kidney then details..
You can do this with managing hierarchies [categories] like parent main group and it's child sub group in JSONArray and details in JSONObject. (This change would be on sender side -server)
You need to load only one url to get all data.
So you don't need to load with different urls like now your doing. But beware this would be a big Json. So when you store it separate the categories and required data [screen-wise requirements] and store in local storage so easy for access.
Another approach would be you have to provide your [category] subgroup names to load so the loading would be like firing same URL with different category names to get data and store it in local storage.
This may lead to fire around 10-15[depends on your categories] urls may affect the UI thread response.
This won't need any changes on your server side response.
**
Programmatically approach to load urls sequentially:
**
URL Loading: This method will get detail of particular category [id or anything
works for you]. This will fire a http request and return a result.
getCategoryDetails(category){
url = url+category;
return $http({
method: 'GET',
url: url,
headers: --
}).then(function onSuccess(response) { //<--- `.then` transforms the promise here
//You can ether store in local storage
return response
}, function onError(response) {
throw customExceptionHadnler.getErrorMsg(response.status, response.data);
});
}
Parallel : This method will do it in parallel, we just load categories[ids] as we have all of them and then use $q.all to wait for all the urls loading to finish.
function loadUrlsParallel(urls) {
var loadUrls = []
for(var i = 0; i < urls.length; i++) {
loadUrls.push(getCategoryDetails(urls[i]))
}
return $q.all(loadUrls)
}
First API: This method to load first url and then Loading urls in
parallel call above method
getListOfCategories(){
url = url;
return $http({
method: 'GET',
url: url,
headers: --
}).then(function onSuccess(response) { //<--- `.then` transforms the promise here
//You can ether store in local storage or directly send response
return response
}, function onError(response) {
throw customExceptionHadnler.getErrorMsg(response.status, response.data);
});
}
urls : you have to prepare list of urls with appending category to
load after loading first url[expecting this returns you all the
categories you will require in your app beforehand] and pass to
loadUrlsParallel method.
You can write loadUrl methods as per your convenience, here whatever
is given is foe example purpose so may not run as it is.
You can load API responses every where from local storage where you've stored after API calls, So this will not ask you to execute API calls on every laoding of pages[screen]
Hope this helps you and solves your prob.

Best practice for multiple AJAX API calls that require a response from the previous call?

I'm working on an internal page that allows a user to upload a CSV with resources and dates, and have the page add all the scheduling information for these resources to our management software. There's a pretty decent API for doing this, and I have a working model, but it seems...cludgy.
For each resource I have to start a new session, then create a new reservation, then add resources, then confirm that the reservation isn't blocked, then submit the reservation. Most of the calls return a variable I need for the next step in the process, so each relies on the previous ajax call.
Currently I'm doing this via nested ajax calls similar to this:
$.ajax('startnewsession').then($.ajax('createreservation').then('etcetc'))
While this works, I feel like there has to be an easier, or more "proper" way to do it, both for cleaner code and for adaptability.
What you're doing is correct, assuming you can't change the API you are communicating with.
There's really no way of getting around having to do some sort of nested ajax calls if you need the response data of the previous one for the next one. Promises (.then) however make it a bit more pretty than having to do callbacks.
The proper solution (if possible) would of course be to implement your API in such a way that it would require less roundtrips from the client to the server. Considering there's no user input in between each of these steps in the negotiation process for creating a reservation, your API should be able to complete the entire flow for creating a reservation, without having to contact the client until it needs more input from the user.
Just remember to do some error handling between each of the ajax calls in case they should fail - you don't want to start creating the following up API calls with corrupt data from a previously failed request.
var baseApiUrl = 'https://jsonplaceholder.typicode.com';
$.ajax(baseApiUrl + '/posts/1')
.then(function(post) {
$.ajax(baseApiUrl + '/users/' + post.userId)
.then(function(user) {
console.log('got name: ' + user.name);
}, function(error) {
console.log('error when calling /users/', error)
});
}, function(error) {
console.log('error when calling /posts/', error)
});
Short answer: as usual I'm trying to do some chains like this:
ajaxCall1.then(
response => ajaxCall2(response)
).then(
response => ajaxCall3(response)
)
I'm trying to avoid using of when. As usual I (and bet you too) have 1 ajax call (for form submit), sometimes 2 chaining ajax calls, for an example, if I need to get data for table, first query for total rows count, and if count greater than 0, another call for data. In this case I'm using:
function getGridData() {
var count;
callForRowsCount().then(
(response) => {
count = response;
if(count > 0) {
return callForData();
} else {
return [];
}
}
).then(response => {
pub.fireEvent({
type: 'grid-data',
count: count,
data: response
})
})
}
publisher trigger event, and I have all my components updated.
In some realy rare cases, I need to use when. But this is always bad design. It happen in case, when I need to load pack of additional data before of main request, or when backend not support bulk update, and I need to send pack of ajax calls to update many of database entities. Something like this:
var whenArray = [];
if(require1) {
whenArray.push(ajaxCall1);
}
if(require2) {
whenArray.push(ajaxCall2);
}
if(require3) {
whenArray.push(ajaxCall3);
}
$.when.apply($, whenArray).then(() => loadMyData(arguments));

Real time message receiving from server (Database - MySQL, Frontend -Angularjs)

My code is working perfectly , But i need to refresh my page to get
new message . I want the real time message.
Angular controller's function -
$scope.getAllMessages = function() {
$scope.messages=[];
restservice.get('', "api/v1/message/getMessageList").then(function(response) {
if (response != null) {
for (var i = 0; i < response.length; i++) {
$scope.messages.push(response[i]);
}
}
});
}
My UI looks like -
<li ng-repeat="message in messages" class="{{(message.me == '1')? 'me' : 'other'}}">
<div>
{{message.userMessage}}
</div>
</li>
Please make it simple for me and give me a proper guideline/tutorial to do it. I'm bad at front end.
Another way is to use WebSockets.
Javascript has built-it WebSocket object wich you can use. And you also need to handle websockets on your server side. But for messaging websockets or long polling requests looks as more useful technologies, as for me.
WebSockets are very simple and if you want real-time, they could be useful. But you would have to implement websockets backend on your server. Here is a sample of WS usage from the JS side:
var exampleSocket = new WebSocket("ws://www.example.com/socketserver", "protocolOne");
exampleSocket.send("Here's some text that the server is urgently awaiting!");
exampleSocket.onmessage = function (event) {
console.log(event.data);
}
This is example from here: https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_client_applications
You need to call getAllMessages method on interval basis so that you get live data or real time data
Link here
https://docs.angularjs.org/api/ng/service/$interval
check with the above link for Example how to implement
To avoid getting ALL messages again i will considere do another .get maybe like this:
$interval(getLastMessagesFrom, 1000);
function getLastMessagesFrom () {
// Extract the last message_ID from $scope.messages, to do this maybe
// you will need to change your backend code to add the message ID in
// the message object. This deppends on your backend code & backend
// message response.
var last_message_id = $scope.messages[$scope.messages.length-1].id;
restservice.get('', "api/v1/message/getMessageList/" + last_message_id).then(function(response) {
if (response != null) {
// Now make sure that your server only returns messages with
// ID higher than last_message_id
for (var i = 0; i < response.length; i++) {
$scope.messages.push(response[i]);
}
}
});
}
Anyways this is still a bad practice if you have a lower time interval (1000 = 1 second) and a lot of concurrent users.
Considere use sockets to avoid repeated and unecessary calls.
use nodeJS's sockets
where you can achieve real-time experience.
e.g
once you inserted to database,you can fire JS command to emit msg realtime
io.sockets.connected[setsocketid].emit("clssifiedMessage", {msg:"Hi!!!how are yyou?" });

Angular dealing with incorrect cached data

Okay this might be a long post but please do not click away you may know a simple answer.
The case:
Lets say you have build an angular app where people log into the system do some operations and then might log out again. The application will collect data from an API using a factory and service and in order to make the application load even faster you save these data in variables like such:
app.factory("divisionService", function (api, $http, $q) {
var division = {};
var divisionArray = [];
var mergedUserList = [];
return {
getList: function () {
var d = $q.defer();
if (divisionArray <= 0) {
$http.get(api.getUrl('divisionWithUsers', null))
.success(function (response) {
divisionArray = response;
d.resolve(divisionArray);
});
}
if (divisionArray.length > 0) {
d.resolve(divisionArray);
}
return d.promise;
},
This will make sure that if the user attempts to use a controller that uses the divisionService then that user will instantly get the data if it is already fetched.
The issue:
Now the user log's out and another user logs in (without refreshing / reloading ) the page. Once the controller calls this factory it already thinks that it has the correct list meaning that return would be the same as the previous user however this data might be incorrect!
Since all angular services are singletons the service will not be destoryed upon logout even though it should.
The obvious answer
An answer to this question might be: "Well then don't store the data in a variable" and since this will work enormous amount of data might make content of the page load slowly.
So my question is what do you do in the above situation? do you really have to deal with loading the data every time it is request or does angular provide a smart way to solve this problem?
Create a clear() function
Add a clear() function to your divisionService factory which will be responsible to empty the cached data structures (arrays, objects, ...)
app.factory("divisionService", function () {
var division = {};
var divisionArray = [];
var mergedUserList = [];
return {
clear: function(){
// Clear the cached data
for (var key in division)
{
delete division[key];
}
divisionArray.length = 0;
// ...
},
getList: ...
}
});
And call this function from when you logout
function logout(){
divisionService.clear();
}
Refresh the application
You can also refresh the entire application when you logout if you don't want to deal with clearing the cached data (e.g. calling divisionService.clear())
function logout(){
$window.location.reload();
}
this will cause the entire application to be reloaded, and all of the temporary (variable based) cached data will be cleared
Marc,
My first thought is just run
divisionArray = [];
On logout. Let me know if that works. If not, I'll look into it further.
You can cache the user information as well and compare it to see if the user has changed before deciding to refresh the data.

In AngularJS, how would you cache the data (for example, a list of top 10 news of today) you receive from Java?

I am fetching the list of news from the Backend and want to cache them and display them in HTML5 using the AngularJS.
I am new to Angular and don't know the correct way to do this. is it possible to cache this data or not. Please suggest.
if are not allowed to comment here you can comment here as well : http://blog.grepruby.com/2014/09/in-angularjs-how-would-you-cache-data.html
Simplest approach for your problem would be making a angular service and using $cacheFactory.
app.service('NewsService', function ($http) {
return {
getTopTen: function () {
// Get top 10 news from backend
return $http.get('/api/news/top10', {
cache: true;
});
}
};
});
NewsService.getTopTen(); // 100ms
NewsService.getTopTen(); // 1ms
$cacheFactory.get('$http').get('/api/news/top10'); // cached item
$cachefactory doesn't support XY what now ?
If you need something more flexible then use :
http://jmdobry.github.io/angular-cache/
If you just want to avoid unecessary calls to your webservice, do not rebuild caching in your application. You should rather use HTTPs caching functionality and your browser support of it by setting the correct HTTP caching headers in your Server:
http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html

Categories

Resources