Angular won't apply object changes to it's properties - javascript

I have an object like this:
$scope.user = {key: 'j16er48', name: 'Foo', title: 'Bar'}
And the template is:
<div ng-if="user.key">{{user.name}}</div>
Now, if I change the value of key property to null or false directly, then the div won't be displayed. But If I change the whole object to this:
$scope.user = {key: false, name: 'Foo', title: 'Bar'}
Nothing happens. Angular seems to still watch the old object and the old value of key property.
I also tried to use $scope.$apply() after assigning a new object to user but still no chance.
Am I missing something?
== UPDATE ==
After so much tests I found a very strange behavior in Angular scope. The issue happens due to an unwanted assignment by reference (or a two-way binding maybe).
I have an object called defaultUser which is : {key: false, name: 'Foo', title: 'Bar'}. Assuming this:
var defaultUser = {key: false, name: null, title: null};
$scope.user = defaultUser;
// here I change a property of $scope.user
$scope.user.key = 'j16er48';
$scope.resetUser = function(){
// Now defaultUser.key equals 'j16er48'
alert(defaultUser.key);
// That's why this line does nothing
$scope.user = defaultUser;
}
So when I tried to assign defaultUser to user object again, I thought is has been reset, whereas defaultUser has been changed and is equal to user. right now.
Is that normal? Does Angular assume all assignments by reference? Is that a two-way binding? Have I been crazy or what else?
https://jsfiddle.net/amraei/g1b5xomz/3/

It seems that you are using an old version of Angular.
I've created two Fiddles, the first is using Angular 1.1.1 (https://jsfiddle.net/dcg74epp/) and the second using Angular 1.2.1 (https://jsfiddle.net/6vy4jn6q/).
They both have a very minimal controller and don't follow any style guidelines, but should get your point across.
app.controller('TestController', function ($scope) {
$scope.user = {key: 'j16er48', name: 'Foo', title: 'Bar'};
var secondUser = {key: false, name: 'Foo', title: 'Bar'};
var tmp;
$scope.changeUser = function () {
tmp = $scope.user;
$scope.user = secondUser;
secondUser = tmp;
};
});
So, if using a newer version of Angular is possible for you, use it.

Related

Ember issue with setting attribute value

I have an Ember Route class defined as below;
export default Ember.Route.extend({
model: function() {
var compObj = {};
compObj.gridPara = this.get('gridPara');
return compObj;
},
gridPara: function() {
var self = this;
var returnObj = {};
returnObj.url = '/myService';
// setting some other returnObj attributes
var summaryObj = {
total: {
label: "Total 1",
value: "100"
},
additional: [{
label: 'Label 2',
value: 'val2'
}, {
label: 'Label 3',
value: 'val3'
}]
};
returnObj.summary = summaryObj;
return returnObj;
},
actions: {
dataLoaded: function(resp) {
// Here I get the service response and want to set (or overwrite) the summaryObj values
this.get('gridParams').summary.total.value = resp.numRows;
}
}
});
My template looks like
{{my-grid params=this.gridPara dataLoaded="dataLoaded"}}
Now I want to set the "summary" on returnObj
I have verified that I get "resp" inside dataLoaded callback.
But I get the following error when trying to do
this.get('gridParams').summary.total.value = resp.numRows;
Uncaught Error: Assertion Failed: You must use Ember.set() to set the value property (of [object Object]) to 100.
Also how do I set/push for "additional" array inside summaryObj
As the error states, you must use set to the the value (Im assuming you have gridParams defined somewhere?):
this.set('gridParams.summary.total.value', resp.numRows);
In order to push a new object, try this:
var additional = this.get('gridParams.additional');
additional.push({label: ..., value: ....});
this.set('gridParams.additional', additional);
not 100% sure, but give it a try:
Watch out the property names. I suppose it's a wording error to declare 'gridPara' and trying to get 'gridParams'
You should retrieve the value like this
this.get('gridParams.summary.total.value')
What you are trying with the last sentence is a setting, but like it was plain JS. In Ember you should do it this.set('gridParams.summary.total.value',resp.numRows)
Just adding to #Remi answers ,the best practice would be to use
Ember.set('gridParams.summary.total.value', resp.numRows);
To answer the question in your comment
Say you want to update additional array at index i.Just do
var updateItem = additional[i];
Ember.set(updateItem.propertyname,newValue)
//Here propertyname would be the property you want to update and new Value is the new value which you want to set to that property

javascript: add nested attribute in object

Wit this example object:
obj = {
id: '123',
attr: 'something'
}
Now I want to add the attribute link in the attribute data. Sometimes data is already existing, but in this example data doesn't exist.
So if I do
obj.data.link = 'www.website.com';
I get the error TypeError: Cannot set property 'link' of undefined.
Result should be:
obj = {
id: '123',
attr: 'something',
data: {
link: 'www.website.com'
}
}
You need to create the data object first:
obj.data = {};
obj.data.link = 'www.website.com';
or you can do it all at once:
obj.data = {
link: 'www.website.com'
};
if data may or may not already by there, there's a handy shortcut that will use the existing one if it's there or create a new one if not:
obj.data = obj.data || {};
obj.data.link = 'www.website.com';
That uses the JavaScript's curiously-powerful || operator.
You need to initialize the data property. You can do something like this:
var obj = {
id: '123',
attr: 'something'
};
obj.data = {};
obj.data.link = 'www.website.com';
In the case for the property existing you can check before assigning link.
if (!obj.data) {
obj.data = {};
}
And as stated in another answer you can use the or operator which I've heard is 'curiously powerful' =]
obj.data = obj.data || {};
That basically means if this value ( obj.data ) exists use it and if it doesn't use the right operand ( {} ). This works because of short circuit evaluation.
Javascript From 1.8.5 you can use the following method:
Object.defineProperty(obj, "data", {
value: {'link' : 'www.website.com'},
writable: true,
enumerable: true,
configurable: true
});
Good luck :)

Angular unintentional binding/object mirroring

So I am working on a project using AngularJS where I need to be able to compare the values of an object in the scope with it's previously recorded values. I'm doing this through an algorithm such as the one below:
function() {
var data = [
{ id: 1, key: 'value', foo: 'bar'},
{ id: 2, key: 'value', foo: 'bar'}
]
$scope.oldTarget = data[0];
$scope.target = data[0];
}
Now if I were to do:
function() {
$scope.target.foo = 'fighters';
if ($scope.target != $scope.oldTarget) console.log('Target was modified');
console.log($scope.target);
console.log($scope.oldTarget);
}
It will output:
{ id: 1, key: 'value', foo: 'fighters'}
{ id: 1, key: 'value', foo: 'fighters'}
My assumption is that AngularJS is automatically binding the two variables target and oldTarget and mirroring any changes done to target to oldTarget. Is this the case, and if so, is there anyway for me to prevent this? If not, what am I doing that's causing it to do this?
This is not related to Angular, it's default JavaScript behavior. You are referencing the same object. If you intend to modify it without changing the source, you need to clone the object.
Take a look:
What is the most efficient way to clone an object?
Most elegant way to clone a JavaScript object
I assume that this is not angular, this is just how it works, because $scope.oldTarget and $scope.target both is links to the same object.
var test = {foo : 'bar'};
var newTest = test;
newTest.foo = 'changed';
console.log(test);
Th output is: "Object {foo: "changed"}"
http://jsfiddle.net/rf0ac6zf/
Looks like your array element is being referenced "by reference". So create new instances of the element like this:
$scope.oldTarget = $.extend(null,data[0]);
$scope.target = $.extend(null,data[0]);

ko.mapping observableArray always trigger subscription

I am working with ko.mapping plugin in order to map data coming from an ajax request.
Setting the key i expect that subscription is not triggered in this case but it's always raised; i can't understand why. Thx in advance.
var arraySource = [{ Prop: 1, Prop2: 1 }, { Prop: 2, Prop2: 2 }];
var mappedArray = ko.observableArray([]);
mappedArray.subscribe(function (data) {
console.log(data);
});
window.setInterval(function () {
ko.mapping.fromJS(arraySource, {
key: function (data) {
return data.Prop;
}
}, mappedArray);
}, 3000);
Demo: http://jsfiddle.net/xvzAj/
Based on the comment in the docs it sounds like passing the third parameter to .fromJS will overwrite the properties of the array which would trigger the notification.
ko.mapping.fromJS(data, {}, someObject); // overwrites properties on
someObject
Source: http://knockoutjs.com/documentation/plugins-mapping.html
In the knockout.mapping.js ln 627, the array contents are replaced which is triggering the subscription notification.
mappedRootObject(newContents);
https://github.com/SteveSanderson/knockout.mapping/blob/master/build/output/knockout.mapping-latest.debug.js
As #Andrew Walters suggested the subscription will always be triggered, because the entire array is overwritten with the new content.
I found a way to recognoze what really changed by reading the knockout release 3 : http://blog.stevensanderson.com/2013/10/08/knockout-3-0-release-candidate-available/
var myArray = ko.observableArray(["Alpha", "Beta", "Gamma"]);
myArray.subscribe(function(changes) {
// For this example, we'll just print out the change info
console.log(changes);
}, null, "arrayChange");
inside the subscription it's possible to get the added, deleted and retained elements in a very simple way !

How to get a value from an array by name instead of key-number

I've created an object and added an asset b345 with some properties in it:
asset = [];
asset.push({'b345' : { 'prop1':'value 1', 'prop2': null}});
I want to push some more assets later, dynamicaly into the same asset-object. So that the asset object holds all assets generated in the code.
Later on in my code I want to retrieve the array associated with one of the entries via the unique identifier b345. But that seems not to work.
asset['b345'];
But that results in an undefined. But If I try to get the data via asset[0] It returns the right object.
How could I arrange this container object asset so that I can
1- easliy add new objects
2- retrieve the object via the identifier?
ps: I found this: https://npmjs.org/package/hashtable but it is only usefull for large storage; and it says it can be done through objects only. But I can't find how it works :(
If you have no need to iterate through your list of objects in some consistent order, then you probably shouldn't make an array in the first place; just make an object:
var asset = {};
asset['b345'] = { 'prop1':'value 1', 'prop2': null};
If you do need to have both array behavior and key-lookup, an efficient way to do it would be to make an array and an object:
var assets = {
list: []
, map: {}
, push: function(key, value) {
map[key] = value;
list.push({ key: value });
}
};
Then:
assets.push('b345', { 'prop1': 'value 1', 'prop2': null });
Subsequently assets.list[0] will be { b345: { prop1: 'value 1', prop2: null }} and assets.map['b345'] will be {prop1: 'value 1', 'prop2': null}.
You'd want to make it a little more sophisticated to deal with updates properly, but that's the basic pattern.
Instead of using an array you use an object
asset = {};
asset['b345'] = { 'prop1':'value 1', 'prop2': null};
then asset['b345'] will give you { 'prop1':'value 1', 'prop2': null}
There's many way to do this. To simply fix this problem:
var asset = new Object; // new object
asset['b345'] = {'prop1':'value 1', 'prop2': null}; // an object property = new object (wrapped in {})
Another way, is:
asset = {'b345' : { 'prop1':'value 1', 'prop2': null}};

Categories

Resources