Strongloop promise inside loop - angularjs

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')});

Related

Easy about Angular: Why does it change order?

Just a 'silly' question but it's confusing me a lot, for a student of AngularJS.
When console.logging a variable, let's say var result is a promise from a GET or whatever, and .then() I do some tasks, like asignin a variable.
var result = ... ;
$scope.number = {};
result.then(function(data) {
$scope.number = 5;
console.log($scope.number);
});
console.log($scope.number);
Okay!
So to me, this should print
5
5
(In case I initialise a $scope variable, outside this function or promise)
5
undefined
(in case the second .log tries to print a non-defined global variable, outside the function)
So I get:
undefined
5
Why does the outter $scope.number print before the inner (5) variable?
I want to initialise the variable after getting the GET, within the promise, and later on, use the initialised var.
Thank you =) This will speed up my learning!
The main thing to understand is that the following returns a promise immediately:
var result = ... ;
Right after that, you pass a function to the .then method of the promise:
result.then(...)
And right after that you do:
console.log($scope.number);
Then later - once the asynchronous action (like an HTTP request) completes and the promise resolves - the function you passed to .then finally executes:
function(data) {
$scope.number = 5;
console.log($scope.number);
}
So promises exist to help you manage asynchronous behavior. They really start to shine when you have to manage multiple asychronous functions:
asyncFunction1().then(function() {
// This executes after asyncFunction1 completes
return asyncFunction2();
}).then(function() {
// This executes after asyncFunction2 completes
});

AngularJS how to update a JSON value with a dynamic key and an $http response?

I have this JSON example:
$scope.channels = {
"ch1": {
id: "ch1",
data: {}
},
"ch2": {
id: "ch2",
data: {}
}
};
QUE 1
How do I update the data: {} part with the dynamic key ?
something like:
for ( var c in $scope.channels ) {
$http.get(JSON_PATH + c + '.json').then(function(res){
// DOES NOT WORK
$scope.channels[c]["data"] = res.data;
// ALSO DOES NOT WORK
var section = $scope.channels[c];
section.data = res.data;
$scope.channels[c] = section;
});
}
The result of $scope.channels is now to have a new section called "data" instead of being under the key == "ch1" (i.e).
In addition, Sublime Text 3 also gives a warning which I'm not sure why? ( it's not like I'm using this or something:
181 don't make functions within a loop
QUE 2
I solved the above problem by actually create an external function and call it within the loop.
So I was wondering why the above code in Que1 doesn't work, while this does:
function load_data(id) {
$http.get(JSON_PATH + c + '.json').then(function(res){
var section = $scope.channels[c];
var section.data = res.data;
$scope.channels[c] = section; // WORK
});
}
for ( var c in $scope.channels ) {
load_data(c);
}
In Que 1 the for loop has continued it's cycle while the $http method is asyncronously processing. The "c" variable has lost it's original context and value. I believe it is destroyed once the for loop completes.
In Que 2, "c" is now "id", and has been passed in as a static value that will continue to be available to anything inside the function closure regardless of asyncronous activity. The function makes a reference to "c" and ceases to care whether the loop destroys it or not.
This has to do with function closures, and will need someone with a better understanding to explain it in detail.
You should change "c" to "id" in Que 2 though, inside your function.
Also, as a semi-related side note, if you are going to loop a series of $http calls, you might want to look up the $q.all documentation and read about how to build promise arrays where you can detect resolution of all calls rather than just individual calls. It can be quite useful.

How does Protractor executes the function calls. (E2E AngularJS + Protractor + Jasmine)

I am new to protractor and currently experimenting with an internal Angular JS application. In the below snippet of code, I am unable to understand on how Protractor is executing the statements and function calls. The behaviour seems weird OR I am doing something completely wrong. Appreciate all the help.
describe('Describe Function', function()
{
it('Should return a value',function(){
var IndexValue = ' ';
var col_model = element.all(by.repeater('col in renderedColumns'));
var row_model = element.all(by.repeater('row in renderedRows'));
browser.get('URL');
ptor = protractor.getInstance();
var user = element(by.model('login.user_id_1'));
user.sendKeys('demo');
element(by.id('txtusername')).getAttribute('value').then(function(text) {
console.log(text); // This line prints 'demo'
});
var pwd = element(by.model('login.password_1'));
pwd.sendKeys('demo');
var submit = element(by.className('login-submit'));
submit.click();
browser.driver.sleep(1000);
var colCount = element.all(by.repeater('col in renderedColumns')).count(); 
colCount.then(console.log) // This prints '80'
var items = [];
getArrayList();
function getArrayList() // Function to capture all the content of the table in an array
{
console.log('Array List function started');
colCount.then(function(Col_count){
for(var i=0; i < Col_count; ++i){
var grid = browser.findElement(by.repeater('col in renderedColumns').row(i));
grid.getText().then(function(gridValue){
items.push(gridValue.trim());
console.log('For loop')
});
}
});
console.log('Array List function completed');
}
console.log(items); // This prints []
getGridValue('Prospect');
function getGridValue(name)
{
console.log('Inside grid value');
}
});
});
When I execute the above code, even before the browser is invoked and the application is launched, first 4 lines (specified below) are printed on the console. What surprises me is the fact that point 1 and 3 are part of the function call which indeed is printing point 7. Looks like all the independent "console.log" are executed first and then the "console.log" associated with "element." statements are executed. Its really confusing. Plz let me know where am I going wrong. Thanks.
OUTPUT ON THE CONSOLE
1. Array List function started
2. []
3. Array List function completed
4. Inside grid value
5. demo
6. 80
7. For loop
For loop
For loop
For loop
.... so on until the loop ends
At first: well, protractor does, what you told it to do... You may read a bit about promises and async JavaScript.
I try to explain a bit, please understand, that explaining everything would take a lot of time.
Starting protractor calls your script (spec). The most lines you wrote before calling getArrayList() are function calls which return promises. This means, the function is called, and when ready, it's callback function gets called (e.g.: then(...))
An example:
var colCount = element.all(by.repeater('col in renderedColumns')).count();
colCount.then(console.log) // This prints '80'
In this two lines you are searching the DOM for every element which can be located by col in renderedColumns, then, if ready, count them and return the value to its callback then, afterwards print it via console.log.
But all of this takes time, and this is the reason why console.log('Array List function started'); gets printed before colCount.then(console.log).
Hope I could help a bit, as mentioned before, you may read a bit about promises.

Re-use array collection on multiple pages

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.

Iterating through parent entity to return array of child entities

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

Resources