46

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.

Mischa
  • 2,069
  • 4
  • 23
  • 32

5 Answers5

58

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.

antoinestv
  • 3,286
  • 2
  • 23
  • 39
  • @AntoineEsteve, thanks for the explaination, can you help to explain why it is working in this plnkr? Here i also update the reference, i thought it shouldn't work ,but ... http://plnkr.co/edit/7atCfQAnTbikUaGtgYi8?p=preview – huan feng Feb 07 '17 at 08:09
  • 1
    @huanfeng In your plnkr you did not overwrite the `list` in a child scope. You have only 2 scope levels: the controller and the `ng-repeat` childs; and you are updating the `list` in the same scope (the controller's one) that the one you used to instantiate it. So there is no prototype inheritance problem: your are updating the same object property than before (the same '$scope' object). The expression `i in list` still resolve as you wanted. – antoinestv Feb 07 '17 at 13:19
  • "$0 is the selected element in Chrome" - thanks! Never knew that :) – Nate Anderson Jan 14 '19 at 16:09
23

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.

Oswaldo Zapata
  • 657
  • 8
  • 9
8

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

sh6210
  • 4,190
  • 1
  • 37
  • 27
4

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.

Ryan Saunders
  • 359
  • 1
  • 13
1

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
    });
  }
};
Ed Knowles
  • 1,925
  • 1
  • 16
  • 24