João Augusto
João Augusto

Reputation: 2305

angular ng-repeat track by $index

I'm having some trouble understanding why if I use track by Index in a ng-repeat, after removing a item from a array I get the "wrong" values in the scope of a directive.

As you can see in the below plunker if I remove the first Item in the array tracked by index, the value in the directive scope will not be the initial one.

I was expecting to have after the removal: 2 - b 3 - c

but I get 2 - a 3 - b

If I don't use track by index I get the right values.

This is the Script

nsTests.controller('nsTestCtrl', ['$rootScope', '$scope', '$filter', '$timeout',
function ($rootScope, $scope, $filter, $timeout) {

    $scope.data = [
     {
         id: 1,
         name: 'a'
     },
     {
         id: 2,
         name: 'b'
     },
     {
         id: 3,
         name: 'c'
     },
    ];

    $scope.data2 = [
     {
         id: 1,
         name: 'a'
     },
     {
         id: 2,
         name: 'b'
     },
     {
         id: 3,
         name: 'c'
     },
    ];

    $scope.removeByIndex = function (index) {
        $scope.data.splice(index, 1);            
    }

    $scope.removeByItem = function (item) {
        var index = $scope.data2.indexOf(item);
        if (index !== -1) {
            $scope.data2.splice(index, 1);
        }
    }
}
]);


nsTests.directive('test', function ($compile) {
return {
    restrict: 'E',
    template: '{{value}}',
    scope: {
        data: '='
    },
    controller: function ($scope) {
        $scope.value = $scope.data.name;            
    }
};
});

Here is a plunker with the problem

Upvotes: 1

Views: 3703

Answers (2)

Michael
Michael

Reputation: 315

using track by index, angular will reuse nodes

This is effectively the issue; the directive is not reinitialised as Angular seems ignore it's value parameters and keep the original data from the index.

E.g.

var arr = [
  { id: 0, status: "Pass" },
  { id: 1, status: "Fail" }
];

If you repeat this data and have a custom directive in the repeat that would display a green dot for pass and a red dot for fail.

Example: https://plnkr.co/edit/ytQ9p6YJOfrDubCGIc2N?p=preview

.pass {
  color: green;
}

.fail {
  color: red
}

.pass,
.fail {
  font-size: 24px;
}
<table>
  <th>ID</th>
  <th>Status</th>
  <tr>
    <td>1</td>
    <td class="pass">&squf;</td>
  </tr>
  <tr>
    <td>2</td>
    <td class="fail">&squf;</td>
  </tr>
</table>

When applying a filter e.g. via $filter on arr that filters out the the object with ID:1

.pass {
  color: green;
}

.fail {
  color: red
}

.pass,
.fail {
  font-size: 24px;
}
<table>
  <th>ID</th>
  <th>Status</th>
  <tr>
    <td>2</td>
    <td class="pass">&squf;</td>
  </tr>
</table>

As you can see, 1 was filtered out, but the result is now incorrect. This is because Angular is being a little lazy and not reinitialising the directive that we are using even though the data we passed in is now different.

Upvotes: 0

Satyam Koyani
Satyam Koyani

Reputation: 4274

I think my plnkr and explanation will help you to understand these thing better way.

Here issue is not with the track by $index

nsTests.directive('test', function ($compile) {
    return {
        restrict: 'E',
        template: '{{value}}',
        scope: {
            data: '='
        },
        controller: function ($scope) {
            $scope.value = $scope.data.name;            
        }
    };
});

You have created isolated scope for you test directive and you are assigning value of data variable to your test directive's $scope.value. Directive is compiling only once so once it compiles it will bind the value after than any change in parent scope's variable is not affecting here because you created isolated scope and copied value from parent's scope.

For better understanding of directive check out this article

Upvotes: 1

Related Questions