I currently have 2 pages, page1.php and page2.php, each of the pages uses a controller that completes its own functions etc.
However there are tabs within the pages that are exactly the same that gets a promise from a factory within the module. The lists are exactly the same except for querying on different IDs. For example both controllers have this:
pageListFactory.getData().then(function (result) {
$scope.BasicItems = result; $scope.basicItemsList = [];
angular.forEach($scope.BasicItems, function (BasicItem) {
angular.forEach(BasicItem['SomeInnerArray'], function (BasicSomeInnerItem) {
if (BasicSomeInnerItem == VARIABLE_THAT_CHANGES) {
$scope.basicItemsList.push({
ID: BasicItem.ID, Title: BasicItem.Title
});
}
});
});
});
So this code is used, and the VARIABLE_THAT_CHANGES is just what changes each time. However as I said it is used on multiple pages, is there a way to create just one function call and then each page just can call a specific bit and send the variable with it?
I tried using $rootScope but of course this just clogs and slows, and I'm not exactly happy on constantly using $rootScope to pass the $scope.basicItemsList around as the list could get quite big.
So is there any way to reuse this code without just copying and pasting it into each controller?
Sure you can re-use it...
Convert the factory to a service, its basically a name change, create a local variable to store the data, update the data on first call, and then grab the data if it exists on the second call.
.service('myService', ... stuff ... { // I suggest using a service, as I don't know if a factory would act as a singleton
var myData = null;
return {
getData: function(){
if(myData != null)
return myData; // returns data
else {
return $http()... // ajax call returns promise
}
},
setData: function(dataToSet){
myData = dataToSet;
}
}
Then your controllers:
//controller 1
var promiseOrData = pageListFactory.getData();
if(promiseOrData instanceOf Array){ // or whatever data you expect
$scope.BasicItems = promiseOrData;
....
}
else { // should be a promise
promiseOrData.then(function (result) {
pageListFactory.setData(result); // set the data once you get it.
$scope.BasicItems = result; $scope.basicItemsList = [];
....
}
}
In controller 2 you only need to get the data as the returned data will be an array, not a promise
On top of all this, write a directive which will process the data when you pass it along, then you can pass the variableThatChanges and have the directive take care of it.
Use services and write the function in that service and pass the variable VARIABLE_THAT_CHANGES into it. By this you can reuse the code.
Related
Preamble
I had a thought: What if I reduced my duplicated data structures by stringing together my objects as microservices, and why don't I use Sails, nodejs and api calls. This is where it all started.
The problem definition
How can I send a dynamic array to a function, execute an unknown number of API calls to another microservice, then combine the returned values into a single object for processing?
The Journey
I have found material about q or bluebird.
I have a function (in sails) that returns me a list of users email addresses in a JSON array.
getUserInfo: function(opt, callback){
var https = require('https');
var options = {
hostname: opt.hostname || 'as.net.au',
port: opt.port || 443,
path: opt.path || '/developers',
method: opt.method || 'GET',
headers: opt.headers || {'Authorization': 'Basic ' + 'ThuperThecretKey'}
}
var req = https.request(options, function(res) {
var data = '';
res.setEncoding('utf8');
res.on('data', function(chunk) {
//console.log('BODY: ' + chunk); //oh so chunky
data += chunk;
});
res.on('end', function() {
//console.log(data)
callback(null, data)
})
})
req.on('error', function(e) {
console.log('problem with request: ' + e.message)
callback(e) //error
})
req.end()
}
It gives me back a set of email addresses in a JSON array:
['dev1#thisplace.com','dev2#thisplace.com','dev3#someotherplace.com']
and I stick this into a json object called "devs".
So now I want to pass this array to a function and have it execute an API call for every dev, and add the results into either the json object, or a new one. I assume a new one as devs is an array not a complete object.
The consensus from the Internet seems to be to use bluebird, but I am struggling with what I assume everyone struggles with, what variable is named what, which one gets updated, how to load things into the variable, where it goes, etc, etc, my head hurts. There are things going in entrances that I am not sure should be going there.
So I am asking for help. Does anyone have a example of the best way to do these asynchronously and then parse the results?
I got far enough to see a nice loop of asynchronous calls going out to my API (thats below), but how do I pass in and read a variable in the last then function?
It logged "Executing a promise for dev1#thisplace.com..... etc etc"
Then the "devs" object empty and the "dev" object as expected (each email address).
But how do I pass back to the calling json (or a new json) the output from My API call? How does it maintain and asynchronously update an object?
I assume something goes in the else after the error (Potential placeholder number 1).
Something like:
promise.map(devs, function(dev) {
console.log('executing a promise for ' + dev)
MyCont.getUserInfo({'path': '/developers' + dev}, function (err,devs) {
if (err) {
console.log(err)
} else {
console.log ('devs: ' + devs)
console.log('dev: ' + dev)
//Potential placeholder number 1 -Some here that loads the returned value to the passed in or new array?
}
})
}).then(function() {
//Or maybe it goes here?
console.log("done");
});
You touched on the first obvious solution, using Bluebird's map. First thing would be to use promises everywhere:
// make this a Promise
getUserInfo: function(opt) {
// your new best friend for issuing http requests without callbacks
var request = require('request-promise');
// the options have to change a little
var options = {
uri: 'https://'
+ opt.hostname || 'as.net.au' + ':'
+ opt.port || 443
+ opt.path || '/developers',
method: opt.method || 'GET',
headers: opt.headers || {'Authorization': 'Basic ' + 'ThuperThecretKey'}
}
// much shorter, right?
return request(options);
}
devs.map(function(dev) {
console.log('executing a promise for ' + dev)
return MyCont.getUserInfo({'path': '/developers' + dev});
}).then(function(results) {
// your results are here, in order
console.log(results);
});
I am going to assume, after reading all that, your question is this:
How can I resolve an existing promise using the results of a bunch of asynchronous functions calls?
First, start with the function inside your outermost promise:
var processEmailAddressList = function (addresses) {
// This is inside a promise.
// It will return when all addresses have been processed.
return Promise.map(addresses, processSingleAddress);
};
var processSingleAddress = function (address) {
// How you implement this is up to you -- and up to whatever
// promise library you are using. return a promise that resolves
// to the value you want
};
Here, we are using map, but we could use reduce. There are some extremely important performance characteristics between map and reduce (see below), but I will ignore that right now. All you need to know is that you are returning a promise that will resolve or reject when all of the items in addresses have been processed by the callback, processSingleAddress.
The question is how to write processSingleAddress.
If you can process that single address using in a way that already uses promises, then it is simple. Maybe you are using a library that is already Promise-ready. In this case, you just call that function directly.
var processSingleAddress = function (address) {
return addressProcessorWithPromise(address);
};
But if you cannot process your list items with promises, you need to figure out a way to do so using promises you create yourself. Here, for example, I am assuming that you are making an asynchronous call to getDataFromAsyncProcess() and that function takes a single address and a node-style callback.
var processSingleAddress = function (address) {
// input is `sample#example.com`
return new Promise(function (resolve, reject) {
getDataFromAsyncProcess(address, function (err, data) {
if (err) reject(err);
// example output is {address: 'sample#example.com', status: 'logged in'}
resolve(data);
});
});
};
At this point, you should expect things to run like this:
var addresses = ['sample1#example.com', 'sample2#example.com']
processEmailAddressList(addresses)
.then(function (list) {
// see below for the value of `list`
})
.catch(function (err) {
console.error(err);
});
Inside then, list should now look like this:
[
{address: 'sample1#example.com', status: 'logged in'},
{address: 'samples#example.com', status: 'logged in'},
]
I know this looks potentially confusing with the amount of indirection that appears to be going on (functions inside functions), but welcome to JS. And the more you get used to using promises, the easier it gets.
THe last thing to be really careful about is how map or reduce (or any other promise-library iterator) does things. If you have, 5 addresses and the process is not so expensive, this is not so critical, but if you have hundreds or thousands of things to process or the processor is expensive (database calls, network requests, memory consumption, etc.), you can end up chewing up a ton of memory and killing your database if all of those addresses are processed in parallel (all at once). If this is a factor, you should try to use an iterator that does things one at a time. Consult your library API for details.
Last note: please try to keep your questions simple. That was unnecessarily verbose.
I am trying to call a loopback find function inside of a for loop, passing in a value from the iteration into the loopback function. The main issue of the code can be represented by the following:
for (var a = 0; a < $scope.countries.length; a++) {
$scope.getEmFacPurElec($scope.countries[a], 'ton/kWh', 'CO2e').then(function(result) {
emFacPurElecToUse = $scope.emFacPurElecs;
}
And here is the function being called:
$scope.getEmFacPurElec = function (country, unit, ghgType) {
var defer = $q.defer();
$scope.emFacPurElecs = [];
$scope.emFacPurElecs = Country.emFacPurElecs({
id: country.id,
filter: {
where: {
and: [
{unit: unit},
{ghgType: ghgType}
]
}
}
});
defer.resolve('Success getEmFacPurElec');
return defer.promise;
};
The problem is that the loopback promise function is called and then returned undefined which means that it moves to the next iteration of the for loop before getting the value to assign to emFacPurElecToUse. I need to do some more calculations with that variable for that country before moving to the next country.
I have looked at using $q.all as a possible solution and also using array.map as per http://pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html (Rookie mistake #2: WTF, how do I use forEach() with promises?), but I just cannot figure out how to pull it all together to make it work. Should I be using a forEach instead?
I also saw this link angular $q, How to chain multiple promises within and after a for-loop (along with other similar ones) but I do not have multiple promises that I need to process inside the for loop. I need to retrieve the value of one emFacPurElecs for that country, do some work with it, then move to the next country. I feel I am close but I just cannot get my head around how I would code this particular functionality. Any help is greatly appreciated.
It seems to me that you do have multiple promises to process inside your for loop, as you say "I need to do some more calculations with that variable for that country before moving to the next country." This should all be done with in the promise chain that I've suggested - calcEmFacPurElec.
$scope.calcEmFacPurElec = function (country, unit, ghgType) {
$scope.getEmFacPurElec(country, unit, ghgType).then(function(countryEmFacPurElecs) {
// do something with countryEmFacPurElecs
return countryEmFacPurElecs;
}
$scope.getEmFacPurElec = function (country, unit, ghgType) {
var defer = $q.defer();
defer.resolve(Country.emFacPurElecs({
id: country.id,
filter: {
where: {
and: [
{unit: unit},
{ghgType: ghgType}
]
}
}
}); );
return defer.promise;
};
Hopefully the above is a pointer in the right direction!
When you want to carry out a promise chain on an array of items, then as you have identified, Promise.all (using whatever promises implementation you require) is what you want. .all takes in an array of Promises, so in your for loop you can do:
var promises = [];
for (var a = 0; a < $scope.countries.length; a++) {
promises.push($scope.calcEmFacPurElec($scope.countries[a], 'ton/kWh', 'CO2e')); // new promise chain that does all of the work for that country
}
$q.all(promises).then(function(arrayofCountryEmFacPurElecs) {console.log('all countries completed')});
I am trying to build an array of entities from a server query that exist in an object arrays
The diagram below illustrates my model:
In my datacontext, I've applied the following code:
function getByDboardConfig(dboardConfig) {
var busUnitDims = [];
var busUnitsTotalCount = dboardConfig.busUnits.length;
var buCount = 0;
dboardConfig.busUnits.forEach(function (busUnit) {
eq.from('BusUnitDimensions') // eq = breeze.EntityQuery
.where('busUnitId', '==', busUnit.id)
.using(em).execute() // em = EntityManager
.to$q(succeeded, failed); // using Angular, thus to$q
});
function succeeded(data) {
buCount++;
data.results.forEach(function (result) {
busUnitDims.push(result);
});
if (buCount === busUnitsTotalCount) {
console.log(busUnits.length);
return busUnitDims;
}
}
}
When I log to the console as show the length of the array, I get the correct entity count, but when I return the result of this call to my controller I get undefined. Not understanding why?
I've tried returning $q.when(busUnitDims) as well but I still get undefined.
The problem with your code is that the function is not returning anything, even if you relocate the return line outside the succeeded block
, it may return before filling the array is finished(notice you're executing the query asynchronously )
Neverthless, looking at your code; I take it you are getting the busUints and then query for each of their line items BusUnitDimensions separately;
that could mean many roundtrips to the server.
Anyways, you aim to fetching busUnits along with their related BusUnitDimensions (eager loading).
Although you didn't provide an idea of how your view model or your controller looks like; I assume you have one view for DboardConfig and another view for both related busUnits and BusUnitDimensions
so your workflow is:
Get a list of DboardConfigs
for each DboardConfig, load it's busUnits along with BusUnitDimensions
Hence, your function could be :
function getByDboardConfig(dboardConfig) {
return eq.from("busUnits")
.where("dboardConfigId", "==", dboardConfig.id)
.expand("BusUnitDimensions")
.using(em).execute() // em = EntityManager
Then inside your controller:
$Scope.getBusUnitsandDimentions = function () {
dataservice.getByDboardConfig($Scope.dboardConfig)
.then(succeeded)
.fail(failed);
};
function succeeded(data) {
$Scope.busUnits = [];
data.results.forEach(function (result) {
$Scope.busUnits.push(result);
});
}
}
}
This way, you can remove the coding for fetching busUnits since we've already fetched it with it's related child table.
Simply put:
busUnits to get your array of bus units for a specified DboardConfig
busUnits.BusUnitDimensions to get it's related dimensions
I'm sharing a service across multiple controllers. I've made this simplified fiddle to illustrate it: http://jsfiddle.net/ziaxdk/FFgpX/
app.service("common", function() {
var first = 1, second = 2;
return {
first: first,
second: second,
// {{model.first+model.second}} calculate this down here??
calc: function() { return first + second; } // This line is not watched
}
});
How can I create calculated rules/properties on the service object so they get reflected to the view?
Regards,
Kenneth
It is not really AngularJS-specific problem it is how closures work in JavaScript for primitive types. Basically when you've got a closure over a primitive type you are getting a copy of the original values instead of a reference to them. You are breaking the link. So in your code you've been changing different variable values then ones used to do calculation.
You could modify your service as follows:
app.service("common", function() {
return {
first: 1,
second: 2,
calc: function() { return this.first + this.second; }
}
});
And your code becomes operational.
Here is a working jsfiddle: http://jsfiddle.net/e3nzY/2/
I am new to extjs and was working on creating a dynamic screen depending on the no of records from the Ext.data.store(); getTotalCount/getCount are used to get the no of records from the store. I need to store the total no of records in a var and return it
I am trying to do something like this
function Get_count()
{
var num;
CacheStore.on({
'load':{
fn : function(store,records,options){
num = getTotalCount();
//console.info('Store count = ', tsize);
//console.info(' count = ', getCount());
},
scope: this
},
'loadexception' : {
fn : function (obj,options,response,e){
//console.info('error = ', e);
},
scope : this
}
});
// this is a wrong logic but have to do something similar
//return num; //return num
};
tsize = Get_count();
I always get null in tsize. I also tried getCount() instead of getTotalCount() butI am getting the same problem.
dont know where I am going wrong
Your logic is a bit poked here. You can't fire a function that will add a listener to a store that will hook when the store has finished loading. ( well you can, but this is a subtle bug down the line ).
What you need to do is declare a listener on the store when you create it, that contains the function you wanna use the number in.
cacheStore =Ext.create...
cacheStore.on('load',function(store,records,e){
//dosomestuff that needs the count
var num= store.totalCount()
//now you use the num in here, else you create an async error
//or you can ...
my.someFunc(num);
//in here, but you can only run it after the store has loaded
},this);