15

I have an array of items that is displayed in a table using ng-repeat. When you click on an item, that item is pulled from the server and the table should then be updated with the updated item.

Function to get the updated item when clicking on an item in the table:

$scope.getUpdatedItem = function(item){
    itemService.getItem(item).then(
        function(updatedItem){
            item = updatedItem;
        },
        function(error){
            //Handle error
        }
    );
};

I'm displaying the items using:

<tr ng-repeat="item in myItems">

The problem: The item in the table is never updated.

What's the best way to update the item in the ng-repeat? Can i use "track by $index" in the ng-repeat for this? Or do I have to iterate over myItems to find the item I want to replace?

Update:

A possible solution is instead of using

item = updatedItem,

to use:

var index = $scope.myItems.indexOf(item);
$scope.myItems[index] = updateItem;

However, I feel that there should be a "cleaner" way of doing this.

gusjap
  • 2,397
  • 5
  • 24
  • 38

4 Answers4

14

There isn't a much cleaner way (then your update). As you noticed, when you change item in your callback function you change the local reference, and not the original item in the array.

You can improve this a bit by using the $index from the ng-repeat, instead of calculating it yourself:

<div ng-click="getUpdatedItem(item, $index)"> </div>

And in your controller:

$scope.getUpdatedItem = function(item, index){
    itemService.getItem(item).then(
    function(updatedItem){
        $scope.myItems[index] = updateItem;
    },
    function(error){
        //Handle error
    }
    );
};

You can also use angular.copy instead but it's much less efficient:

function(updatedItem){
    angular.copy(updateItem, item);
},
eladcon
  • 5,815
  • 1
  • 16
  • 19
  • 1
    Usage of $index should be avoided as it is ng-repeat index and not the actual array index of the scope object which is bound to the view; Sometimes this can go horribly wrong in case you are doing sorting or filtering for example you have an array bird[0] = parrot; bird[1] = crow ; if you filter for crow, your grid will result in $index = 0 but in reality, crow was at index 1. – user1697113 Apr 19 '17 at 07:22
2

If I understand your problem properly

could something like this work?

<!-- template code -->
<table>
    ...
    <tr ng-repeat="(index, item) in items">
        <td>{{item.name}}</td>
        <td>
             {{item.detail}}
             <button ng-if="!item.detail" ng-click="loadItem(index)">
        </td>
    </tr>
</table>

// Controller Code
$scope.items = [...]
$scope.loadItem = function(index){
    itemService.getItemDetail($scope.items[index]).then(function(itemDetail){
        $scope.items[index].detail = itemDetail;
    });
};
Eloims
  • 5,106
  • 4
  • 25
  • 41
  • That might not work due to the scope of `ng-repeat`. It only watches for changes on the object itself and not its properties. – Asta Apr 29 '15 at 16:00
  • I think that when any $q promise is resolved, the whole scope is checked again recursively – Eloims Apr 29 '15 at 20:38
  • Your only updating the properties of `$scope.items` rather than changing the value of it. ng-repeat only has a shallow watch rather than a deep watch so it won't see that those properties have changed. – Asta Apr 29 '15 at 20:58
  • 1
    I just set up a plunker. It does seem to work http://plnkr.co/edit/1O0XFkz5oH4UKRQt0jV1 . ng-repeat does not have a deep watch because it only needs to know when there are new elements, but the {{item.extra}} in the template does i guess. – Eloims Apr 29 '15 at 21:16
  • 2
    Yeah I just created a jsFiddle to check it too and it looks fine (I should have checked this first sorry) - http://jsfiddle.net/neridum/cvdz9r16/ Perhaps by using the index directly it helps to avoid the issue. – Asta Apr 29 '15 at 21:22
  • 1
    I found a good answer on the topic http://stackoverflow.com/questions/15646304/how-does-angularjs-watch-these-cases-in-an-ng-repeat – Asta Apr 30 '15 at 19:38
1

item may start as a reference to an item in your list, but when you say:

item = updatedItem;

You reseat that binding -- you are no longer referring to the item in the list, but to the disconnected one that was returned in your promise. Either you will need to modify the item, like so:

function(updatedItem){
  item.varA = updatedItem.varA
  item.varB = updatedItem.varB
  ...
}

Or, if it gets too hairy, you might consider an item array that looks more like this:

var items = [ 
  { data: item1 },
  { data: item2 },
  { data: item3 }
};

At which point your update function will look like this:

function(updatedItem){
    item.data = updatedItem;
},
Michael Hays
  • 6,878
  • 2
  • 21
  • 17
0

I've just spent hours on this issue. I couldn't use the $index solution from @eladcon, as my ng-repeat also used a filter, to the index isn't correct if the rows/items are filtered.

I thought I would be able to just do this:

$filter('filter')($scope.rows, {id: 1})[0] = newItem;

but that doesn't work.

I ended up iterating the array until I found a match, and then using the $index from the iteration (not from the ng-repeat) to set the array item to the new item.

// i'm looking to replace/update where id = 1
angular.forEach($scope.rows, function(row, $index) {
  if (row.id === 1) {
    $scope.rows[$index] = newItem;
  }
})

See here: https://codepen.io/anon/pen/NpVwoq?editors=0011

Sean
  • 14,359
  • 13
  • 74
  • 124