0

I have an similar app as in the following example and I can't figure out why the source data is not updating. More info is in the example comments. I'm sure this is some trivial issue that I've overlooked.

Controller

    $scope.items = [
        { id: 1, color: 'red', title: 'car' },
        { id: 2, color: 'blue', title: 'sky' },
        { id: 3, color: 'transparent', title: 'nothing' }
    ]
    $scope.favoriteIds = [1, 2, 3]
    $scope.getItem = function(id) { /* returns an item with given id */ }

Finally, there are two methods to modify $scope.items, but only the first one works, because the new item gets not-already-known id.

    $scope.changeData1 = function() {
        $scope.items = [{ id: 666, color: 'ugly', title: 'face' }]
        $scope.favoriteIds = [666]
    }

    $scope.changeData2 = function() {
        $scope.items = [{ id: 1, color: 'ugly', title: 'face' }]
        $scope.favoriteIds = [1]
    }

View

<h1>Favourite items</h1>

<ul ng-repeat="id in favoriteIds" data-ng-init="item = getItem(id)">
    <li>I like my {{ item.color }} {{ item.title }}</li>
</ul>

<button ng-click="changeData1()">Modify data</button>
<!-- prints: I like my ugly face -->

<button ng-click="changeData2()">Modify nothing</button>
<!-- prints: I like my red car -->

The problem is, that I need to use this second way to modify data.

http://jsfiddle.net/4pEpN/7/

Audio
  • 476
  • 6
  • 12

2 Answers2

2

I'm relatively new to Angular as well, so if there's a simple way to do this, I don't know what it is (unfortunately, Angular documentation is atrocious). Regardless, you can avoid this by rethinking the structure of your code (and you'll end up with a better program too).

In your view, you're using ng-init to call getItem on the id during each iteration of your ng-repeat loop. This is what's causing your problem, and it's (apparently) due to an Angular performance feature (more at this question).

Basically, don't use ng-init except to execute something when your app starts. Otherwise, you'll end up with what you've got now: logic in the view (calling getItem(id)) rather than the model, where it belongs.

Instead, use ng-repeat to repeat over the exact data you want to display. Like I said before, this means some code rearrangement. For example, you could use a function to generate the user's current list of items on the fly. Check out this fiddle: http://jsfiddle.net/4pEpN/19/

See my comments in that code for all the changes I made, but the most relevant one is:

$scope.favoriteItems = function() {
    var favObjs = [];
    for (var i = 0; i < favoriteIds.length; ++i) {
        favObjs.push(getItem(favoriteIds[i]));
    }
    return favObjs;
};

then in your view: <ul ng-repeat="item in favoriteItems()">


There are also lots of other approaches you could use. For instance, you could have an update function, which handles anything that might need to be done after any user input (including updating the user's custom array of items). Then you could call this in your changeData functions.

user229044
  • 232,980
  • 40
  • 330
  • 338
user428517
  • 4,132
  • 1
  • 22
  • 39
  • Thanks. Finally I've merged two data collections into single one and then just iterated over it. It started to cause a minor problem because the generate function (like `favoriteItems()` in this example) was called many times for every single data change. Fortunately, this can be handled properly with the `$scope.$watch`, so I left the `favoriteItems` to be an array and used `$watch` to fire update events. – Audio Jul 26 '13 at 21:41
2

I don't think ng-init is appropriate since it only affects template initialization. So how about just calling your getItem(id) for fetching each attribute, like this: http://jsfiddle.net/JrvbD/1/

lossleader
  • 13,182
  • 2
  • 31
  • 45
  • Thanks for the answer, I agree with inappropriate use of `ng-init`. Unfortunately, in a real app the template is much complex than this example, so calling `getItem(id)` many times wouldn't be great for performance. – Audio Jul 22 '13 at 22:19
  • Hmm, I would have suggested the correct way to do this without the repeated calls would be adding a filter based on favoriteIds in the ng-repeat's expression. But in testing it looks like angular treats the filter as static and is really only watching the input to it. You can see the /2/ of my last jsfiddle, but try commenting out the change to items and you'll see it isn't really watching the filter. – lossleader Jul 23 '13 at 18:09