Angular unintentional binding/object mirroring - javascript

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

Related

How calling the raw data reshaping process to form, which we can easily access to desired data?

Typical situation: some application stores data in external file on database. Ideally, the data structure should has such structure as it's easy to access to any desired value from program. But reality is differ: we need to transform raw data to other structure, that could be easy to access from the code. What the name of this transformation process?
Potentially wrong answer: "mapping". As far as I know, the "mapping" is the definition of conformity between two data sets. In this question, we are considering single data set.
Example: we recieved below data:
const rawData = {
foo: {
a: 'asdf',
b: 'nhyt'
},
bar: {
a: 'gfdsa',
b: 'sdasdf'
}
}
But we want that to iterate by b value. So, before that, we need to (most appropriate synonym of "transform" here) it to:
const preprocessedData = { // optimized? mapped? reduced? reshaped?
[rawData.foo.b]: {
name: 'foo',
a: 'asdf'
},
[rawData.bar.b]: {
name: 'bar',
a: 'gfdsa'
}
}
Please note that now we don't discuss HOW to reshape data to make it more reachable. I just want to know, how called process when we make data more reachable. It should be something like "mapping" or "optimization", but none of them is right answer, I suppose.
You can always use for loop, all the function like map, reshape just for reduce your coding workload and make it more readable, it's not necessary. Beside, if you do it right, performance of for loop is better than function like map.
var rawData = {
foo: {
a: 'asdf',
b: 'nhyt'
},
bar: {
a: 'gfdsa',
b: 'sdasdf'
}
}
var preprocessedData = {};
for(key in rawData) {
var obj = rawData[key];
preprocessedData[obj.b] = {name:key, a:obj.a};
}
console.log(preprocessedData);

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

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.

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 :)

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

Extended function jquery

I have a function that looks like this:
jQuery.fn.menuFunc = function( settings ) {
settings = jQuery.extend({
actionAddURL:"",
actionModifyURL:"",
........
},settings);};
where all the parameters i initialize (actionAddURL, actionModifyURL, etc..) are strings (ids of different elements or urls). My question is how do i add some objects there.
myObject:"" doesn't seem to be the way to do it. Any tips?
myObject:[] // empty JavaScript object
,
myOtherObject: { // JavaScript object notation
name: "Bob",
age: 17
}
Probably:
myObject: {}
is what you are looking for.
You can then add properties to it:
myObject.name = "Name";
To define an object you could use the following syntax:
myObject: { stringProp: 'value1', intProp: 10, arrayProp: [ 1, 2, 3 ] }

Categories

Resources