This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 6 years ago.
As you can see in the code I provided in this question, I try to loop over the properties of an object. All properties are empty objects! I also have another object where I stored a couple of rest-calls (ngResource Stuff which is not important for this question).
Here you can see the "Rest-Calls" stored in $scope.restCalls.
$scope.restCalls = [
{
resource1: RestService.call1,
resource2: RestService.call2,
resource3: RestService.call3
},
{
resource4: RestService.call4,
resource5: RestService.call5,
resource6: RestService.call6
},
{
resource7: RestService.call7,
resource8: RestService.call8,
resource9: RestService.call9
}
];
$scope.data symbolizes the data for each tab. Every object in this array holds the data for the tab. All resources are initilized empty and if the user changes to a page the resources will get stored here.
$scope.data = [
{
resource1: {},
resource2: {},
resource3: {}
},
{
resource4: {},
resource5: {},
resource6: {}
},
{
resource4: {},
resource5: {},
resource6: {}
}
];
So far so good. I gurantee that the calls are working fine. In my application there are multiple tabs and so I want to try to implement some lazy loading :D
Therefore I implemented a function:(the index is defined in the html and is only the a number between 0 and 2)
<uib-tab heading="Tab1" select="tabChange(0)">
... HERE I have some tables which access the $scope.data[0] data
</uib-tab>
<uib-tab heading="Tab2" select="tabChange(1)">
... HERE I have some tables which access the $scope.data[1] data
</uib-tab>
<uib-tab heading="Tab3" select="tabChange(2)">
... HERE I have some tables which access the $scope.data[2] data
</uib-tab>
Here you can see the function:
$scope.tabChange = function (index) {
for (var propertyName in $scope.data[index]) {
$scope.restCalls[index][propertyName]().$promise.then(function (data) {
$scope.data[index][propertyName] = data;
});
}
};
Now lets come to the problem description:
When I change the tab the function tabChange gets fired.
Every Restcall gets fired correctly
The results only get stored into the wrong property of $scope.data[index]. Its always the last propertyname. So for example I change to tab2(index 1).
$scope.data will end up like this:
$scope.data = [
{
resource1: {},
resource2: {},
resource3: {}
},
{
resource4: {},
resource5: {},
resource6: RESULT OBJECT OF THE LAST REST CALL!
},
{
resource7: {},
resource8: {},
resource9: {}
}
];
I think that propertyname is not available in the then function. But I have no clue how to get the name into this function.
The problem arises because the propertyName is in the upper scope of the function and its value is changed before the function is called. You can bind your variable to function scope like below.
$scope.tabChange = function (index) {
for (var propertyName in $scope.data[index]) {
$scope.restCalls[index][propertyName]().$promise.then(function (propertyName,data) {
$scope.data[index][propertyName] = data;
}.bind(null,propertyName));
}
};
You can learn more about javascript closures here and in other sources you can find from Google.
Related
Right now, I'm trying to convert a object to a json, but the object has methods in it.
One common solution I've found is storing the function in the json, but that wouldn't work for me (mainly because that would totally break updating)
I think it will be easier to reinitialize the function every time the program is reloaded. What would be the best way to exclude functions when stringifying to json, but including the functions when the json is parsed?
Extra note: some of the functions are stored in arrays and other objects
Edit:
Current code to load json data:
var loadData = JSON.parse(window.localStorage.getItem("slotAuto"));
G = {
...G,
...loadData
};
Current code to save json data:
var toSave = JSON.stringify(G, G.ReplaceNull);
console.log(toSave);
window.localStorage.setItem("saveGame", toSave);
Replace Null function:
if (value == null) {
return undefined;
};
return value
Example object:
{
functionList: [function () {
console.log("do something")
}]
}
Example output:
{
"functionList": [null]
}
I'm currently in a situation where I need to create a few watches based on the properties of an object.
These properties are used to group functions that depend on the same variable / expression.
While creating the $watch functions in the loop, it seems to well, but when the watches actually execute, only the last property is persisted in the function scope. Which means that for all $watches (which evaluate different expressions), the functions that get executed are the same.
for (var prop in obj) {
if (obj.hasOwnProperty(prop) {
$scope.$watch(function() {
return evaluateExpression(obj[prop].expression);
}, function(newVal, oldVal) {
evaluateDependentExpressions(obj[prop].dependentExpressions);
});
}
}
Now if my obj variable looks like this:
var obj = {
'Person.Name': {
expression: ...,
dependentExpressions: [...]
},
'Person.Age': {
expression: ...,
dependentExpressions: [...]
}
};
Then the function evaluateDependentExpressions is called twice in where the value of prop = 'Person.Age'.
Any help how to solve the function scope problem is greatly appreciated.
plunk
This is known problem in JavaScript prop variable is set to last used value in for (var prop in obj), simple workaround is to use IIFE:
for (var p in obj) {
(function(prop) {
// your code
if (obj.hasOwnProperty(prop) {
$scope.$watch(function() {
return evaluateExpression(obj[prop].expression);
}, function(newVal, oldVal) {
evaluateDependentExpressions(obj[prop].dependentExpressions);
});
}
})(p);
}
Explanation here: JavaScript closure inside loops – simple practical example
I'm working with an angular service of the type:
services.factory('SaveHistory', function($rootScope, $localForage){
return {
videoAccessed: function(idPillola) {
$localForage.getItem('trainings_user_'+$rootScope.user.id)
.then(function(succ, err) {
for (var item in succ) {
[].forEach.call(succ[item], function(el, index) {
el.pillole.forEach(function(el, index){
if (el.idPercorso == idPillola) {
console.log(idPillola);
el.tracking.completion_status = 1;
}
});
});
}
var newTrainings = succ;
...
});
}
When the function is fired with the correct idPillola , console.log logs the correct idPillola value one single time, so it seems that the cycle works correctly. But : if the attribute in the object (object or rather 'el' in the nested forEach cycle) that i want to change is a primitive , there are no problems, if the attribute is not primitive but an another object attribute, like tracking.completion_status in this case, all elements are updated ! (Like the if control had been ignored).
It is related to Angular or Javascript itself?
I have this javascript object:
return {
AccDocs: {
query: function() {
...
},
deleteAndQuery: function() {
...
AccDocs.query(); //Error: AccDocs is not defined
}
}
}
But, it returns an error that says AccDocs is not defined.
How can I achieve something like this?
Variables and properties on objects are different things. You cannot access the property of an object without specifying which object you mean.
You can probably access it using the this keyword:
this.query();
Keeping in mind that the value of this will vary depending on how the function is called (when a.b.c.d.AccDocs.deleteAndQuery() is called, this inside deleteAndQuery will be AccDocs as it is the first object to the left of the last ., but if you were to first copy query to another variable and then call query(), pass it to setTimeout, or if you were to use call or apply then the value of this would change).
For more robustness (but less flexibility, since being able to change the context can be useful) you can store your object in a variable which you can access by name.
var AccDocs = {
query: function() {
...
},
deleteAndQuery: function() {
...
AccDocs.query();
}
};
return { AccDocs: AccDocs };
By using the this keyword:
return {
AccDocs: {
query: function() {
...
},
deleteAndQuery: function() {
...
this.query(); //Here
}
}
}
I have certain objects that I have to delete certain properties ie:
objA = { firstAttrA: 'fooA', secondAttrA: 'barA' }
objB = { firstAttrB: 'fooB', secondAttrB: 'barB' }
I want to pass these objects in a function that will delete the firstAttrA and firstAttrB based on the following properties file:
{
"objA":"firstAttrA",
"objB":"firstAttrB"
}
The method needs to be robust, I need to avoid excessive looping and anything that will affect performance since the amount of objects that will essentially be passed is great and their properties numerous.
Essentially i suppose I need to do a delete objA.firstAttrA; delete objB.firstAttrB; but driven by a JSON properties file.
Well if defined in the global scope then your method would look like this:
var objRef = {
"objA":"firstAttrA",
"objB":"firstAttrB"
};
for (var item in objRef) {
if (window.hasOwnProperty(item)) {
if (window[item].hasOwnProperty(objRef[item])) {
delete window[item][objRef[item]];
}
}
}