ng-repeat not updating on update of array - angularjs

I'm rendering data through an ng-repeat cycle. And i would like it to update as I update the array. From what i read this should happen automatically however this is not working. So what am i doing wrong?
html:
<tr ng-repeat="data in dataDifferenceArray">
<td>
{{data.name}}
</td>
<td>
{{data.startData}}
</td>
<td>
{{data.endData}}
</td>
<td>
{{data.differenceData}}
</td>
</tr>
Controller (this function is triggered on a button with ng-click):
$scope.getDifferences = function () {
$scope.dataDifferenceArray = [];
var i;
for (i = 0; i < 20 ;i++) {
$scope.dataDifferenceArray.push({
name : 30,
startData : 20,
endData : 2,
differenceData : 30
})
}
}
Console.log reveals that the array is updated correctly, however the table in my view does not change. I don't know what i'm doing wrong.

That's because you change the array reference in your method getDifferences.
To avoid that, us dot, for example with "controller as" syntax:
<div ng-controller="myController as c">
[...]
<tr ng-repeat="data in c.dataDifferenceArray">
<td>
{{data.name}}
</td>
<td>
{{data.startData}}
</td>
<td>
{{data.endData}}
</td>
<td>
{{data.differenceData}}
</td>
</tr>
[...]
If you want to understand how scopes work, i would advice this article : https://github.com/angular/angular.js/wiki/Understanding-Scopes#ngRepeat
Another solution could be :
$scope.getDifferences = function () {
$scope.dataDifferenceArray.length = 0; // here ----
var i;
for (i = 0; i < 20 ;i++) {
$scope.dataDifferenceArray.push({
name : 30,
startData : 20,
endData : 2,
differenceData : 30
})
}
}
but in this solution, you need to create the array outside (and only once) : $scope.dataDifferenceArray = [];
Edit2: My answer was not really clear, let's try to understand what is happening in deep:
Q: Why does the ng-repeat still has the reference REFERENCE1 ?
You have to remember that there is not only 1 scope in your template.
Eg: the ng-repeat directive create new scopes for each of the repeated elements, but we can still access to the parent scope in each child scope. Angular implemented this behavior using Prototype Inheritance: each child scope inherit the properties of its parent scope thanks to its prototype.
You can experiment how it is working by inspecting one on your child elements, then enter in the console: $($0).scope() (it will give you the scope of the selected element, $0 is the selected element (Chrome)). You can now see that there is the same object in $($0).scope().$parent and $($0).scope().__proto__, it is your parent scope.
But there is one problem with prototype inheritance: Let's say we have A = {}; B = {C: 1}, if A inherits from B then A.C == 1. But if we affect a new value A.C = 2, we did not change B, only A.
Angular expressions are evaluated using the current scope as this. So if we have something like ng-click="dataDifferenceArray = []" it is equivalent to this.dataDifferenceArray = [] with this being the scope of the element where ng-click is.
This problem is solved when you are using controller-as because it injects the controller in the scope and you will never directly affect a property to the scope.
Let's take back our example: A = {}; B = {C: {D: 1}}, if A inherits from B then A.C.D == 1. And now even if we affect a new value A.C.D = 2, we changed B also.

even when properly updating the array reference, I was having the same problem, ng-repeat was not rendering my changes. Finally I found the solution by calling to $scope.$apply();
$window.wizard.on("readySubmit", function () {
var newArray = GenesisFactory.GetPermissionsFromTemplate(GenesisFactory.SecurityModules, true);
$scope.NewSecurityProfileInstance.Permission.length = 0;
newArray.forEach(function (entry) {
$scope.NewSecurityProfileInstance.Permission.push(entry);
});
$scope.$apply();
});
I hope this can help.

I've done nothing but added this line "$scope.$apply()" after pushing the element, and I'm done.

For those who have an array being set within scope (i.e. $scope.$apply() causes an error if called after a push), but it is still not updating correctly on the DOM, my workaround has been to call this function immediately after the push/slice/array update:
function applyArray(container, key) {
setTimeout(function() {
var tempArray = container[key];
container[key] = [];
$scope.$apply();
container[key] = tempArray;
$scope.$apply();
}, 0);
}
By passing in container and key, where container in this case would be $scope and key would be "dataDifferenceArray" (note the quotes).
I have no idea why the DOM does not update correctly in some instances. It shows the correct number of elements, and it updates when a new element is added, it just doesn't update the child elements DOM correctly (i.e. the newly pushed element may take on the DOM view of the previous child). This could be due to how we redefine this to vm in Johnpapa's Angular Style Guide, but I'm not confident in that.

Actually it looks like your array is being set to empty inside the click function. This will work if you move your arrays scope outside the function.
Example
// Declared outside
$scope.dataDifferenceArray = [];
// Your function
$scope.getDifferences = function () {
var i;
for (i = 0; i < 20 ;i++) {
$scope.dataDifferenceArray.push({
name : 30,
startData : 20,
endData : 2,
differenceData : 30
});
}
};

Related

using ng-click to update every cell in a table column

I'm new to Angular and I was trying to figure out how to have the data in one of the columns in a table I made convert from one number format to another when the user clicks on the top of the table cell. I've create a filter already, but i don't know how to call it so it effects all the cells in the table.
<tr ng-repeat="x in data">
<td>{{x.id}}</td>
<td>{{x.name}} </td>
<td>{{x.desc}}></td>
<td>{{x.number}}</td> <--- THIS IS WHAT I WANT TO CONVERT
</tr>
I'm not even sure where to start with this. I basically have a ng-click directive call "convert" which I've defined in the controller. I know that if I define a variable in the $scope (such as $scope.foo = "1") and then call the convert() function I can replace the value like this:
$scope.convert = function(){
$scope.foo = 2;
}
And then my table updates with that value. But what if every table cell in that column has a different value, and I basically want to run that value through a filter I've made.
Any suggestions on how to approach this?
You said you already have a filter?
Then just give your filter an argument 'numberFormat':
angular.
module('yourModule').
filter('yourFilter', function() {
return function(input, numberFormat) {
// convert the input according to the numberFormat
return filteredValue;
};
});
Then you can update the format in your convert() scope-method:
$scope.convert = function(){
$scope.numberFormat = 'long';
}
and pass it to your filter:
<td>{{x.number | yourFilter:numberFormat}}</td>
BTW:
Read about controllerAs - IMHO it is a better practice to store values at the controller rrather than directly on the scope.
Your function simply needs to update number property of the each object in the data array. You could do it like this:
$scope.convert = function() {
$scope.data.forEach(function(item) {
item.number = item.number + 2; // Convert number somehow
});
};
I assume click handler on table header of column as below
<th ng-click="convert()">column of number</th>
in the controller write the function as below
$scope.convert = function() {
$scope.data.forEach(function(obj) {
obj.number += 1;
})
//$scope.$apply() use this is template is not refreshed
}

Repeat an item in ng-repeat

I have an array of objects whereby there is a need to allow the user to add an item that is a duplicate of another. It is triggered when they increase the quantity where a property called 'HasPers' is true.
The HTML markup looks like:
<td width="10%"><input type="number" value="{{productdetails.Quantity}}" ng-model="productdetails.Quantity" min="1" ng-change="change(productdetails)" /></td>
and the function is:
$scope.change = function (item) {
if (item.HasPers) {
item.Quantity = 1;
$scope.items.push(item);
}
};
Initially this had a problem in the repeater wherby it knew it was a duplicat object but this was solved by adding 'track by $index' in the repeater
<tr ng-repeat="productdetails in items track by $index">
http://mutablethought.com/2013/04/25/angular-js-ng-repeat-no-longer-allowing-duplicates/
but the item is clearly still associated with the original since when I change some properties on the original one they change on the duplicate one too. I really want to avoid typing out all of the properties again as in
How to add new key in existing array in angular js?
is there any way to create a new item that is identical to the one being passed in but is a different object?
try
$scope.change = function (item) {
if (item.HasPers) {
var copyItem=angular.copy(item);
copyItem.Quantity = 1;
$scope.items.push(copyItem);
}
};
Try by using angular.copy
$scope.change = function (item) {
if (item.HasPers) {
var newItem = angular.copy(item);
newItem.Quantity = 1;
$scope.items.push(newItem);
}
};

AngularJS ng-repeat splicing array not updating DOM correctly

I'm running into an issue when using ng-repeat to create buttons from an array that is populated through a drag/drop system.
Here is a screenshot of the page. You're suppose to use the Numbers and Symbols to create an expression in the dropzone (gray area). In the screen shot I have a pre tag that is displaying the contents of the array. There is only one object in that array but the repeat is still displaying two. Is there something I'm not understanding about how ng-repeat handles itself when its array is updated?
$scope.arr = [];
//gets called when a number or symbol is dropped
$scope.dropItem = function(draggable, dropzone){
if(dropzone) {
var obj = {
name: draggable.name,
fragmentId: draggable.fragmentId,
controlId: draggable.controlId,
id: hal.utils.generateGuid()
};
$scope.arr.push(obj);
$scope.$apply();
}
};
//callback for draggables inside dropzone
$scope.reorderItem = function(draggable, dropzone){
//reorder
if(dropzone){
}
//remove
else {
for(var i = 0; i < $scope.arr.length; i++){
if($scope.arr[i].id == draggable.value){
$scope.arr.splice(i, 1);
$scope.$apply();
}
}
}
};
HTML
<td class="equation-drop-zone" hal-dropzone="PerimeterEquation" max="10">
<pre>{{arr}}</pre>
<button
ng-repeat="button in arr track by button.id"
hal-draggable="{{button.name}}"
value="{{button.id}}"
hal-text
control-id="{{button.controlId}}"
fragment-id="{{button.fragmentId}}"
drop-callback="reorderItem"
></button>
</td>

Why my angularjs filter does not apply some time when the object property change

I have array of object say person which have two property name and hideMe. i have make a directive to add, remove person from array and use filter to hide the person using property hideMe.
service
app.factory("personService", function () {
var person = function () {
this.name = "";
this.hideMe = false;
};
var persons = [];
return {
add: function () {
persons.push(new person());
},
hide: function (index) {
persons[index].hideMe = true;
}
}
});
controller
app.controller("personCtrl", function ($scope, personService) {
$scope.model = personService;
});
HTML
Add New Person
<table>
<tr>
<td>Name </td>
<td>remove</td>
</tr>
<tr ng-repeat="person in model.persons | filter : { hideMe: false}" >
<td>
<input type="text" ng-model="person.name">
</td>
<td>Hide Me </td>
</tr>
</table>
when i hide the person by click on hideMe link some time it is not hide ????
here is the fiddle http://jsbin.com/kolaraliki/1/
The reason you are experiencing this error is because of the use of $index.
$index gives the index of the current item in the iterator ng-repeat, not the index of the source element. As soon as an item is removed from the array, the $digest triggers the ng-repeat to re-calculate, and $index of the items in the ng-repeat are re-calculated. Thus, if you have 3 items in your array, at 0,1,2 but item 1 is hidden, you would expect your ng-repeat would have 2 items, 0,2 but it actually has 0,1. clicking to hide 2 would actually try to hide 1 in the array, which is already hidden.
instead of using $index, try instead model.hide(model.persons.indexOf(person)) to get the actual array index instead of the iterator index.
I forked your jsbin with an example: http://jsbin.com/jecosofequ/2/edit
It is because you have already filtered only those persons who have hideMe value as true to be shown and by clicking link which calls hide() you are simply setting hideMe value as true for second time. You can simply fix this by changing that filter to be filter: { hideMe: false } in your ngRepeat and then it should work. Because it would show's only those objects which hideMe values are false.

In an ng-repeat, is the iterator offset of $index dynamic to the results of a filter

In an ng-repeat, is the iterator offset of $index dynamic to what is visible? I am getting seemingly incorrect $index values when a filter is applied.
Working with no filter applied:
Not appearing to work with filter applied (Note the console log):
When a filter is removed:
And finally my ng-click call:
<a ng-click="showHideOrderDropDown($index)" href="">
Show More<br/><i class="icon-arrow-down"></i>
</a>
Click handler:
$scope.showHideOrderDropDown = function(index) {
console.log(index);
$scope.data[index].orderDropDown = !$scope.data[index].orderDropDown;
};
Now I can easily work around this, but I was just hoping for some clarification.
After doing some research it appears that applying a Filter actually adds and removes (not hides) elements from the ng-repeat therefore the $index would apply to the new order of the array and no longer reflect the $scope array object.
Since asking the question I went ahead and passed the database id to the controller instead.
$scope.showHideOrderDropDown = function(id) {
for (var i = 0; i < $scope.data.length; i++) {
if ($scope.data[i].id === id) {
$scope.data[i].orderDropDown = !$scope.data[i].orderDropDown;
}
}
};

Resources