Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Handling duplicate elements in an ng-repeat

I am building an app which features a kind of "playlist". This is represented an ng-repeated custom directive with ng-repeat = "element in playlist"

Because I want to allow a user to re-use the same element twice in the playlist, I tried using the track by $index addition.

Now, what's confusing is: when I came to remove an element from the playlist (I have a function removeElement(index) which essentially contains something like this:

$scope.removeElement = function(index){
  $scope.playlist.splice(index, 1);
}

Something weird happened: the element was removed correctly from $scope.playlist, but for some reason the view didn't update properly. The last element appeared to be removed.

Furthermore, I couldn't properly re-order the elements in the array either.

When I removed track by $index this problem disappears, so I assume this is because when you remove an item from the array, if you're only looking at the indices, then it appears you've just deleted the last one.

The behaviour is odd though, because transcluded content is correctly removed -- see this plunker

EDIT: The above link has been modified to show the problem better and also show the answer I settled on.

The question has also been slightly edited, to make it clearer what I was getting at. KayakDave's answer below is still correct, but is more suited to an array of primitives (which my original plunker featured).

TL;DR: How do you put duplicate elements in an ng-repeat without sacrificing the ability to control their position, or remove elements correctly?

like image 441
Ed_ Avatar asked Nov 28 '13 16:11

Ed_


People also ask

How do you prevent duplicates in NG-repeat?

You can use unique filter while using ng-repeat . If you use track by $index then unique won't work.

How do I filter in NG-repeat?

The ng-repeat values can be filtered according to the ng-model in AngularJS by using the value of the input field as an expression in a filter. We can set the ng-model directive on an input field to filter ng-repeat values.

What does ng-repeat do?

Definition and Usage. The ng-repeat directive repeats a set of HTML, a given number of times. The set of HTML will be repeated once per item in a collection. The collection must be an array or an object. Note: Each instance of the repetition is given its own scope, which consist of the current item.

Does ng-repeat create a new scope?

Directives that Create Scopes In most cases, directives and scopes interact but do not create new instances of scope. However, some directives, such as ng-controller and ng-repeat, create new child scopes and attach the child scope to the corresponding DOM element.


1 Answers

One of the big performance advantages of using track by is Angular doesn't touch any DOM element whose tracking expression hasn't changed. This is a huge performance improvement for long ng-repeat lists and one of the reasons for the addition of track by.

That performance optimization is at the root of what you're seeing.

When you use $index in track by you're telling ng-repeat to tie each DOM element it creates to it's position ($index) on the first run of ng-repeat.

So the element with color style red is tagged 0, orange 1, yellow 2 ... and finally indigo 5.

When you delete a color Angular looks at the indexes you told it to track and sees that you longer have an index #5 (since your list is one shorter than before). Therefore it removes the DOM element tagged 5- which has a color style of "indigo". You still have an index #2 so the element with the color yellow stays.

What makes it confusing is that, due to data binding, the text inside the DOM element does get updated. Thus when you delete "yellow" the DOM element with the color yellow gets the text "green".

In short what you're seeing is ng-repeat leaving the DOM element styled with yellow untouched because it's tracking value (2) still exists but data binding has updated the text inside that element.

In order to add multiple entries with the same color you need to add your a unique identifier to each entry that you use for the track by. One approach is to use key-value pairs for each entry where the key is your unique identifier. Like so:

$scope.colorlist = {1:'red', 2:'orange',3:'yellow',4:'green',5:'blue',6:'indigo',7:'yellow'};

Then track by the key as follows:

<color-block ng-repeat="(key, color) in colorlist track by key" color="{{color}}" ng-transclude>
    {{color}}
</color-block>

And make sure to use that key for your delete select:

<option value="{{key}}" ng-repeat="(key,color) in colorlist">{{color}}</option>

Now the DOM element with the color styling yellow is tied to the key you specified for the "yellow" array element. So when you delete "yellow" the ng-repeat will remove the correct DOM element and everything works.

You can see it work here: http://plnkr.co/edit/cFaU8WIjliRjPI6LInZ0?p=preview

like image 108
KayakDave Avatar answered Sep 23 '22 21:09

KayakDave