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?
You can use unique filter while using ng-repeat . If you use track by $index then unique won't work.
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.
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.
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With