Why does pushing object to array override/update existing and alike object? - javascript

My javascript/angular code is performing contrary to what i expect and i can't figure out why. I have an angular service defining two methods for getting and setting items
app.factory('orderItems',['Restangular','$mdDialog',function(Restangular,$mdDialog){
var orderItems = [];
return{
getOrderItems: function(){
return orderItems
},
setOrderItems: function(item,fillings){
...
orderItems.push(item)
...
}, ...
Items are set using ng-click and passing an item and checked addons;
product_template.html
<div >
<md-button class="md-raised" ng-click="showAddons($event,product)">Add to Cart</md-button>
</div>
controller
app.controller('productCtrl',['$scope','getProducts','orderItems','Restangular','$mdDialog',function($scope,getProducts,orderItems,Restangular,$mdDialog){
//$scope.products;
$scope.products = getProducts(Restangular);
// Dialog
$scope.showAddons=function(ev,product){
$mdDialog.show({
locals:{current_Product: product,},
//inline controller
controller:['$scope','current_Product','orderItems',function($scope,current_Product,orderItems){
$scope.product = current_Product;
$scope.addons = {
checked:[],}
...
}],
templateUrl: 'dialog.html',
targetEvent: ev,
})
}
}])
dialog.html
<md-list-item ng-repeat="addon in product.productAddons">
...
<md-checkbox class="md-secondary" checklist-model="addons.checked" checklist-value="addon"></md-checkbox>
</md-list-item>
<md-button class="md-raised" style="background-color:#f44336; color:#fff" ng-click="setOrderItems(product,addons.checked)">Done</md-button>
The problem arises when setOrderItems is passed a similar object more than once. The first time around, orderItems.push works as expected then when a similar product is passed to 'setOrderItems', it is pushed to 'orderItems' but all items in 'orderItems' are updated to this currently set item.
That is, if orderItems was[{name:"chicken burrito",fillings:[{name:"cabbage"},{name:"cheese"}],...}] before, after setting a similar item but with diff fillings, orderItems is updated to [{name:"chicken burrito",filling:[{name:"guac"}],...},{name:"chicken burrito",filling:[{name:"guac"}],...}] . If setOrderItems is passed a different product say [{name:"goat burrito",...}] it is added as expected.
Can't happen to find a similar issue around. What am i doing wrong. I want to be able to add similar items but with different fillings to orderItems.

It's difficult to see exactly with the code you've given. But if I'm following what you are doing correctly, then the problem is probably here:
$scope.products = getProducts(Restangular);
you are getting a set of products, I assume that there is an object for chicken burrito and an object for goat burrito. The problem, I think, is that you are using the same object for every chicken burrito. So when you push it onto the array you are pushing a reference to the object in products, you then edit that object and push it again. But again you are pushing a reference to the same object. If you actually look at your array before the second push, I suspect you will see that the object already on the array is already altered to match the one you are about to push because it's the same object.
To solve this, you need to make a copy of the object. To do that, take a look at this question:
Deep copying objects in angular?
angular.copy should let you copy your product so that you aren't always pushing the same object.
For example, you could do this:
setOrderItems: function(item,fillings){
...
var itemCopy = angular.copy(item);
orderItems.push(itemCopy);
...
}
Now you are always pushing a copy into your array and your array will fill with independent objects.

Related

Update Object $scope item by $parent.$index and $index

What Im Attempting to Do
Im pushing an object into an array, this works as expected. But as I try to update one of the objects in the array via their $parent.$index and $index all objects are updated.
Object Being Pushed Into Array (Multiple Times)
// Array for Objects
$scope.arr = []
// Object to be pushed into Array
$scope.obj = {
content:[
{
type:"text",
data:"This is Dummy Text",
style:{
"height":"500px"
}
},
// Could be more than one Object within Content
]
}
The above object will be pushed into $scope.arr multiple times, within the view the objects are looped.
// Looped Arrays
<div ng-repeat="l1 in arr track by $index">
<div ng-repeat="l2 in l1.content" ng-style="l1.style">{{l1.data}}</div>
</div>
Updating by $parent.$index and $index
So at this point I have pushed the $scope.obj multiple times into $scope.arr and this is where the issue occurs.
I need to update only one of the $scope.obj's in the $scope.arr via a line of the code like the following:
// Set $index's to target the specific array items
var parentIndex = 0
var index = 0
$scope.arr[parentIndex].content[index].style['height']
An example of a possible update would be the following:
var o = parseInt($scope.arr[parentIndex].content[index].style['height'])
var n = o + 1
$scope.arr[parentIndex].content[index].style['height'] = new + 'px'
At the moment the above will update all inserted/pushed objects in $scope.arr despite setting the correct $parent.$index and $index. Where as I need to target and update one, not all.
I must be missing something here, any help or guidance is greatly appreciated.
When pushing, try to do a copy of the object like so:
$scope.arr.push(angular.copy($scope.obj));
Since you keep the important parts of your code secret (how you insert the "objects" into the "array") I can only guess, that you're "inserting" the same object to multiple places (means: you keep the reference to the same object in multiple indexes in the array, so basically you have only 1 object) and then when you change the object in "1 place" using array[1].object.a=2, then you'll see the change in "each" index: array[4].object.a==2, because they refer to the same object actually

AngularJS Removing Item from Array Leaves undefined in place of object

I have been working on an AngularJS project and have come across this issue before. I have the following array in my controller like so:
$scope.items = [];
I then fill this array with objects from a call to our REST API:
$http.get(API_URL_HERE).then(onSuccess);
The onSuccess method is called and the objects fill the array. I now have:
console.log($scope.items) resulting in [Object, Object, Object, ...]
Now when I use the $scope.items in an AngularJS ng-repeat loop it works great, as it should BUT if I try to remove an element from the $scope.items array, it always seems to remove the element BUT replaces it with an undefined value and this undefined value is interpreted by AngularJS in the ng-repeat loop and outputs an empty row in the template with no data. This causes issues if using ng-show / ng-hide for example as even though you have no real objects in the array, it still sees it as full because it is full of [undefined, undefined, undefined ...].
This is causing major headaches and I have seen other people with same issue but the fixes don't seem to work well.
The solution I have found to this, that seems to work well is below. Please also note that some people have said this is just a pure Javascript issue, well I don't think it is, when I tried it within AngularJS and pure Javascript, I got 2 different results with the same setup and data.
For example I have a $scope.remove method which handles removing items from the $scope.items array.
When I do the following code:
$scope.items.splice(index, 1);
It results in this console.log output [Object, Object, undefined, Object, ...].
However, by doing the following code:
$scope.items.splice(index, 1);
$scope.items.pop();
Results in the following output from console.log [Object, Object, Object] and my ng-repeat loop updates as it should without the empty rows.
My solutions seems to work fine but please do let me know if you find anything wrong with it. This code certainly looks cleaner than some of the others I have spotted on different sites and is working across all browsers I have tested.
UPDATE
My onSuccess method looks like this:
var onSuccess = function(response){
$scope.items = response.data.items;
//results in [Object, Object, Object, ...]
};
My $scope.remove method looks like this:
$scope.remove = function(index){
$scope.items.splice(index, 1);
//results in [Object, Object, undefined, Object, ...]
//add the following code
$scope.items.pop();
//results in [Object, Object, Object, ...] the undefined has gone
};
And only when adding in the pop method does it work as it should.

Ember : How to update a single field in an array of JSON objects in a controller?

How can I update Einstein's score to a 100 in the controller?
In a controller, I have an array of JSON objects like :
items = [
{title:"John", score:24},
{title:"Einstein", score:2},
{title:"Mary", score:19}
];
This is rendered in the template using component like this :
{{#each items as |item|}}
{{some-child-component scoreVal=item.score}}
{{/each}}
What should I do to update Einstein's score to a 100? I just want to change that particular field and have it reflect in the app.
I want to avoid replacing the entire array with a new (almost same one), because that causes a refresh for all components in the template.
[FAILED] I tried using :
var allItems = this.get('items');
allItems[1]['score'] = 100; //ERROR
Also
this.set('items[1][score]',100); //ERROR
I discovered the Ember way of doing this:
var elem = items.objectAt(1);
Ember.set(elem, 'score', 100);
You could use .findBy to find the record and give it a new value.
If you aren't using Ember Data for your data layer then I would also make the array of objects Ember Objects so you can use .get and .set to update the attributes.
Here's a full JSBin of what you're trying to accomplish.
Where exactly are you trying to set the score to 100?
If you make your items Ember objects
items = [
{title:"John", score:24},
{title:"Einstein", score:2},
{title:"Mary", score:19}
].map(function(item) { return Ember.Object.create(item) });
you will be able to use the setter as items[1].set('score', 100)

AngularJS - binding array value to input ngModel

I have $scope.myArray, and it's binding with an input field by ngModel and the expression {{myArray}}
My issue is when I modified myArray by call changeMyArray(), the input's value did not change. But the expression {{myArray}} is display new value.
So, Why the expression work but input field does not?
I have a way to do, but I want to find a better approach
var newArr = $scope.myArray;
newArr.push("b");
$scope.myArray = angular.copy(newArr);;
Example fiddle
Basically, I think what you want to do is bind the input to a "new entry" scope variable, and then push the value of that variable to your array when the user clicks "Push To". Here's what I mean:
In controller:
$scope.changeMyArray = function() {
$scope.myArray.push($scope.newEntry);
$scope.newEntry = "";
}
In HTML:
<input ng-model="newEntry">
But actually:
Really what you want is a way to edit the contents of an array via text, and have updates to that array from elsewhere also update the text. This is actually pretty simple since browsers come with a JSON library.
I implemented it by starting with a known pair of objects:
$scope.myArray = [];
$scope.myArrayString = "[]";
That way you can update the string via ngModel:
<input ng-model="myArrayString">
Watch for changes on this model to update the actual array:
$scope.$watch("myArrayString", function() {
$scope.myArray = JSON.parse($scope.myArrayString);
});
Then update the string in the changeMyArray function:
$scope.changeMyArray = function() {
$scope.myArray.push("b"); // Or whatever you would like to add here
$scope.myArrayString = JSON.stringify($scope.myArray);
}
Experiment in my fork of the Fiddle.
What's going on?
The variable $scope.myArray is an object, and any object in Javascript can be converted to a string (most complex objects end up as the unhelpful "[object Object]"). Arrays will actually display their contents when converted to a string, so binding an array to HTML via {{myArray}} is pretty straightforward.
However, the reverse conversion is not as simple. In general, a text input can't be bound to an array in a two-way fashion as we'd like. The solution, then, is to use an intermediary variable to hold the string value, and use $scope.$watch to keep the two values in sync.
So you seem to be wondering why when pushing to the array, your $watch function doesn't do the increment. That's because the #watch function only checks object reference equality.
When pushing to the array, the reference stays the same. When you copy the array and set it again in the same variable, the reference changes.
That's why #watchCollection works as expected and increments when each item is pushed.
I have an explanation for my question. Please correct me if I wrong, very thank.
My Question:
Why "myArray" input field does not update when $scope.myArray is changed (Model doesn't update View)?
<input ng-model="myArray" id="myArray">
The answer is AngularJs ng-model doesn't know $scope.myArray is changed. Because ng-model does not perform a deep watch of object (rather than a string or number), it only looks for a change of identity or compares the reference of the objects.
In my case, $scope.myArray is collection. So, although $scope.myArray has changed by push new item (structure is changed), it's reference does not change.
As the result, $setViewValue() and $render() never invoked to update the view.
$render
$setViewValue
Solution:
Sol1: Add new item to $scope.myArray, make a copy of myArray object and then asign a copy to $scope.myArray again. By this way, the object reference is changed. AngularJs see that change and update the view.
var newArr = $scope.myArray;
newArr.push("b");
$scope.myArray = angular.copy(newArr);
Sol2: Create custome $watch('email', function(){...}, true). The last parameter is TRUE to let Angular perform a deep watch. Then, in watch's listener function, I manually set $viewValue = newValue and invoke $render() of ngModelController to update the view. In case we have Formatters, we should invokes them in this step.
$scope.$watch('myArray', function(newValue, oldValue) {
if (newValue !== oldValue) {
var ctrl = angular.element(document.querySelector('#myArray')).controller('ngModel');
// Invoke formatter
var formatters = ctrl.$formatters,
idx = formatters.length;
while(idx--) {
newValue = formatters[idx](newValue);
}
ctrl.$render();
}
}, true);
Please see my script

How to work with javascript object methods in Angularfire

I have an object that represents a restaurant order:
function order () {
this.customer_name = ''
this.menu = // menu object
}
extended with some object methods for business logic, like:
order.prototype.value = function() {
var total = 0;
for (var i = 0; i < this.menu.length; i++) {
// calculate the order value
}
return total;
}
In the angular controller orders get pushed onto an array when submitted (via ng-click from a button in the view):
var ref = new Firebase('https://myfirebase.firebaseio.com');
$scope.orders = [];
angularFire(ref, $scope, 'orders');
$scope.currentOrder = orderService;
$scope.submitOrder = function() {
$scope.orders.push($scope.currentOrder);
};
Once orders are pushed into the array, properties like orders[0].customer_name work, but methods like orders[0].value() don't.
It seems reasonable that Firebase/Angularfire would only be syncing JSON, but is there an approach that would allow me to keep order-related logic included with the order object, i.e without having to write $scope.getOrderValue(orders[0])?
There isn't a great way to do exactly what you want, since according to the Firebase FAQ:
At a low-level, we support basically the same data types as JSON: Strings, Numbers, Booleans, and Objects (which in turn contain Strings, Numbers, Booleans, and more Objects).
Which means you can store data but not functions. It seems like a clean way to accomplish the same thing would be to store the latest order value as a property of your order object, and have a method as part of your orderService that updates it whenever menu items are added or removed. Alternatively, do what you suggested and have a getOrderValue somewhere, but it probably still makes sense to put that in a service.
I actually had the same issue.
I wanted to add a method to my firebase object.
After looking in the latest angularfire docs I found that $extend can do just that
I didn't test it yet, but I think this is the way to go about it.

Categories

Resources