Easy about Angular: Why does it change order? - angularjs

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

Related

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.

Strongloop promise inside loop

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

run a function once previous function complete

In the success callback to an ajax query, I have to
a) update the property of an object in an array (and I have to locate that object in the array)
b) save that array again in storage
c) broadcast an event
I'm (trying to) accomplishing that by doing this (code below)
updateTheArray(myarray,'date', $scope.date);
myStorage.put(myarray);
$scope.$broadcast('alldone');
As I'm concerned these are happening out of order or could happen out of order,I would like to be able to do something like (in pseudo code) in the success callback
var updated = updateTheArray(); //update a property on an object in array
updated.promise().then(function(){
myStorage.put(array); //saves
}).done(function(){
$scope.$broadcast('alldone'); //broadcast
}
However, the way I've written the storage and the updateTheArray function are not setup to do that.
Is there a way in Angular to make sure the next function only runs once the first one is complete?
Leaving aside the updateTheArray function, even if I try to use the storage function in promise like this
var save = myStorage.put(array);
save.promise().done(function(){
console.log('blah');
});
I get a cannot read property promise of undefined error, so how do I make that storage return a promise object?
updateTheArray (finds a particular object in an array and changes a property)
function updateTheArray(array, attr, value){
var len = array.length;
len = len -1;
for (var i = len; i > 0; i --){
if(array[i][attr] === value) { //find by date
array[i]['completed'] = true; //set some property to true
}
}
}
storage
app.factory('myStorage', function(){
return {
get: function(){
return JSON.parse(localStorage.getItem(STORAGE_ID) || '[]');
},
put: function(myarray){
localStorage.setItem(STORAGE_ID, JSON.stringify(myarray));
}
}
}
As described in this answer, local storage calls are synchronous, so there is no need to wait for myStorage.put to complete.
The reason for the cannot read property promise of undefined error is that you aren't returning anything at all from your put function on myStorage (but, as above, you don't need to return a promise anyway as it's a synchronous call).
The updateTheArray implementation you have shown is also synchronous, so the whole thing should work in order as-is, without any callbacks or promises required.

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.

Resources