Angular dealing with incorrect cached data - javascript

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.

Related

How to the objects to perform calculation in angularjs

I am using angularjs in my project.
I am able to fetch the records from Database and binding in html page.Here I need to get the data from 4 collections in database so I need to perform several server calls to get the data. When i am assigning everything in separate Scope variables. My sample code is below
var click = function(){
$http.get('/CalBuildingget').then(function (response) {
$scope.ViewBuildings = response.data;
});
for (i = 0; i < $scope.ViewBuildings.length; i++) {
$http.get('/CalBuildingFloorget/'+ scope.ViewManageBuildings[i]._id).then(function (response) {
$scope.floorDetails = response.data;
});
}
Here I need to fetch floors for each Building by its Id and store in building scope as an array object and then by floor id fetch again units which again needs to do server calls and assign inside the scope.
How can I achieve that as first it performs the loop then it starts server call of building.
You need to fetch floors in success callback of first request.
So something like this.
var click = function(){
$http.get('/CalBuildingget').then(function (response) {
$scope.ViewBuildings = response.data;
for (i = 0; i < $scope.ViewBuildings.length; i++) {
$http.get('/CalBuildingFloorget/'+ scope.ViewManageBuildings[i]._id).then(function (response) {
$scope.floorDetails = response.data;
});
}
});
You'll mess up the whole performance of your application with approach you are using, are you sure that you want to send HTTP call in loop? think of a case when you have around 1000 records, shall you afford to send 1000 HTTP calls to server? instead why don't you fetch floorDetails in /CalBuildingget/ ?
Never send HTTP calls in loop, think of network bandwidth and application performance.
For multiple subsequent service calls you should always utilise promise concept. conceptually it should be like below:
function callServiceForEachItem() {
var promise;
angular.forEach(items, function(item) {
if (!promise) {
//First time through so just call the service
promise = fakeService.doSomething(item);
} else {
//Chain each subsequent request
promise = promise.then(function() {
return fakeService.doSomething(item);
});
}
});
}
use this link for best practice perform chain service call
you can check this discussion

how to prevent rest API call in controller every time the route/page is visited

I am calling some rest API(s) in my Angular app to get data from server and display it on page (ex: home page). But every time I visit the page, the API is hit, and data is fetched and displayed, because in my Controller, I am calling the rest service. Is there any 'best' way I can 'save' the data on first call and then, when user visits the same page, display the 'saved' data instead of hitting rest services again and again?
Also, there will be instances when I would want to hit the API instead of displaying saved data (ex: when user logs out and logs in again, or when data updates on server). What's the best way with Angular to implement such functionality?
Here is a working Plunker for demo of my current app.
(Please see home.js for service call example).
angular.module('home').controller('HomeCtrl', function($scope, DrinkService) {
var vm = this;
vm.loading = true;
vm.greeting = "Drink World!";
DrinkService.getDrinks().then(function(response) {
vm.drinks = response.data.results;
console.log(vm.drinks);
vm.loading = false;
});
}).factory('DrinkService', ['$http',
function($http) {
var url = 'https://raw.githubusercontent.com/Randmness/DeathByCaffeine-Enyo/master/data/canonDbList_dbService.json';
var drinkService = {};
drinkService.getDrinks = function() {
return $http.get(url).then(function(response) {
return response;
});
};
return drinkService;
}
]);
You can use a service using $q. An example service would be
service('myService', function($q, $http) {
this.data;
var self = this;
this.getMyData = function() {
if (angular.isDefined(self.data)) {
// use $q to return a promise
return $q.when(self.data)
}
return $http.get('myurl').then(function(resp) {
self.data = resp;
})
}
}
The service will set the value from the API the first time it's called and set the value on the service so subsequent calls use the saved data.
In your controller you can call myService.getMyData()
you can see a similar question here
#user2954587 answer will help you. I would also like to add that services are singleton i.e they are created once and hence are perfect to store data, share data among other controllers etc. Controllers on other hand are bound to a view. Each time a view is loaded controller bound to it is loaded too. You also need to remove data from the service some times(in your case when you logout), just set inject myService to the controller where you perform the logout operation and set data to undefined .

Javascript: Schedule HTTP requests for later

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.

Angular JS Service Promise for Massaged Data

Hi I am new to Angular and javascript and need a bit of help.
I have a service that will need to aggregate data from various locations. I am building a sub-service to pull data from one of these locations. This subservice needs to 1) retrieve data from a REST web service, 2) massage it a bit and 3) return the final data to the invoking service.
I have steps 1 and 2 working, however I am running into a problem on the third. In general, I am having a hard time understanding promises. Yes, I've read the documentation, googled around, even saw a cartoon on it, still can't figure it out.... Anyway, here is the relevant code:
app.service('advsolr',['$http',function($http) {
var DEBUG = false;
var conf = get_conf();
var solr = 'serverurl';
var res = {};
var data = {};
this.query = function(searchp) {
//Run Search
query_solr(searchp);
return data;
};
var query_solr = function(search) {
var g = 'serverurl' //works fine
if (DEBUG) { console.log(g);}
$http.get(g).then(function(response){
res = response.data; // this works
parse_search_res(); //this massages the data and sticks it in the data object
return data; //this does absolutely nothing here
});
};
}]);
The main query method is ran by the other service. This queries a Solr instance, gets the results and massages them into the format I want. I know I can do this elsewhere, but I want to have this as a standalone service for portability and plus I just want this to work dammit.
So the query method runs, I had some other stuff in there, but I took it out for this example since it would not add value. It hits query_solr which gets the data and massages it with parse_search_res, which sticks it into the data global variable.
Now the issue is that query method returns the empty data before parse_search_res had a chance to load the data in it. How can I prevent the query method from returning without the data?
Thanks
The idea of promises is that you initiate some asynchronous operation like AJAX request, then you return corresponding promise object, and a consumer code uses this promise's methods to provide callback function on promise state change.
So to fix your code you need to make query_solr return promise:
app.service('advsolr', ['$http',function($http) {
var DEBUG = false;
var conf = get_conf();
var solr = 'serverurl';
var res = {};
var data = {};
var query_solr = function(search) {
var g = 'serverurl' //works fine
if (DEBUG) { console.log(g);}
return $http.get(g).then(function(response){
res = response.data; // this works
return parse_search_res();
});
};
this.query = function(searchp) {
return query_solr(searchp);
};
}]);
You'll also need to change parse_search_res() to return the massaged data instead of saving it into the "data" variable.
And having set up advsolr service like that, one could use it like this:
advsolr.query('something').then(function(data) {
console.log(data);
});

How can I cleanly pull a Parse.Object relation's records when fetching the object?

In the Parse JavaScript guide, on the subject of Relational Data it is stated that
By default, when fetching an object, related Parse.Objects are not
fetched. These objects' values cannot be retrieved until they have
been fetched.
They also go on to state that when a relation field exists on a Parse.Object, one must use the relation's query().find() method. The example provided in the docs:
var user = Parse.User.current();
var relation = user.relation("likes");
relation.query().find({
success: function(list) {
// list contains the posts that the current user likes.
}
});
I understand how this is a good thing, in terms of SDK design, because it prevents one from potentially grabbing hundreds of related records unnecessarily. Only get the data you need at the moment.
But, in my case, I know that there will never be a time when I'll have more than say ten related records that would be fetched. And I want those records to be fetched every time, because they will be rendered in a view.
Is there a cleaner way to encapsulate this functionality by extending Parse.Object?
Have you tried using include("likes")?
I'm not as familiar with he JavaScript API as the ObjC API.. so in the example below I'm not sure if "objectId" is the actual key name you need to use...
var user = Parse.User.current();
var query = new Parse.Query(Parse.User);
query.equalTo(objectId, user.objectId);
query.include("likes")
query.find({
success: function(user) {
// Do stuff
}
});
In general, you want to think about reverse your relationship. I'm not sure it is a good idea be adding custom value to the User object. Think about creating a Like type and have it point to the user instead.
Example from Parse docs:
https://parse.com/docs/js_guide#queries-relational
var query = new Parse.Query(Comment);
// Retrieve the most recent ones
query.descending("createdAt");
// Only retrieve the last ten
query.limit(10);
// Include the post data with each comment
query.include("post");
query.find({
success: function(comments) {
// Comments now contains the last ten comments, and the "post" field
// has been populated. For example:
for (var i = 0; i < comments.length; i++) {
// This does not require a network access.
var post = comments[i].get("post");
}
}
});
Parse.Object's {Parse.Promise} fetch(options) when combined with Parse.Promise's always(callback) are the key.
We may override fetch method when extending Parse.Object to always retrieve the relation's objects.
For example, let's consider the following example, where we want to retrieve a post and its comments (let's assume this is happening inside a view that wants to render the post and its comments):
var Post = Parse.Object.extend("Post"),
postsQuery = new Parse.Query(Post),
myPost;
postsQuery.get("xWMyZ4YEGZ", {
success: function(post) {
myPost = post;
}
).then(function(post) {
post.relation("comments").query().find({
success: function(comments) {
myPost.comments = comments;
}
});
});
If we had to do this every time we wanted to get a post and its comments, it would get very repetitive and very tiresome. And, we wouldn't be DRY, copying and pasting like 15 lines of code every time.
So, instead, let's encapsulate that by extending Parse.Object and overriding its fetch function, like so:
/*
models/post.js
*/
window.myApp = window.myApp || {};
window.myApp.Post = Parse.Object.extend("Post", {
fetch: function(options) {
var _arguments = arguments;
this.commentsQuery = this.relation("comments").query();
return this.commentsQuery.find({
success: (function(_this) {
return function(comments) {
return _this.comments = comments;
};
})(this)
}).always((function(_this) {
return function() {
return _this.constructor.__super__.fetch.apply(_this, _arguments);
};
})(this));
}
});
Disclaimer: you have to really understand how closures and IIFEs work, in order to fully grok how the above works, but here's what will happen when fetch is called on an existing Post, at a descriptive level:
Attempt to retrieve the post's comments and set it to the post's comments attribute
Regardless of the outcome of the above (whether it fails or not) operation, always perform the post's default fetch operation, and invoke all of that operation's callbacks

Categories

Resources