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);
});
Related
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
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.
I have simple table scripts in Azure written in javascript and node.js.
Now, if I call any of these from Windows Phone, the object parameter gets updated automatically from the return value. And thus code like this works
await table1.InsertAsync(val1);
MyObj val2 = new MyObj();
val2.data = val1.Id;
await table2.InsertAsync(val2);
However now I try to utilize this same from scheduled job in Azure: essentially chaining 2 insert calls so that latter depends on the id of the former. The id column is identity and gets created automatically.
How can I achieve this? Scheduled job is written in javascript/node.js. I have tried
var res = table1.insert(val1);
And using val1.id after the first insert, but neither works.
And of course just moments after I post the question I come up with an answer.
table1.insert(val1, {
success: function(res) {
var val2 = {
data: res.id
}
table2.insert(val2);
}
});
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
I have an App that requests a list of possible items from a REST service. I use $http or $resource for that.
Now i want to cache those items in localStorage and only sync my local storage with the backend every now and then to check if anything has changed.
So before i did this:
var getAllPlugs = function () {
var backend = $resource(getURLString() + '/getAllPlugsAvailable');
return backend.query();
};
but now i want the function to return my cached items right away and once the asynchronous http request is done for, it should update the item list if something has changed. This of course should be directly reflected in the UI
The problem if i do something like this:
var getAllPlugs = function () {
var backend = $resource(getURLString() + '/getAllPlugsAvailable');
var result = backend.query();
localStorage.setItem("plugs", JSON.stringify(result));
return result
};
i still only get the result of the http request. But how to achieve it so i get the cached ones first and then that object will be updated with changes. Maybe a success callback from my controller passed to the service that calls the backend? I need some inspriation, sorry if it is trivial...
Return the array from local storage. Once the data is there from http replace the content of the array.
var getAllPlugs = function () {
var results = JSON.parse(localStorage["plugs"]);
var backend = $resource(getURLString() + '/getAllPlugsAvailable');
backend.query({}, function(data) {
results.length=0; //clear existing
angular.forEach(data, function(plug) {
results.push(plug);
}
localStorage.setItem("plugs", JSON.stringify(results));
});
return result
};