diff --git a/src/ng/directive/ngRepeat.js b/src/ng/directive/ngRepeat.js
index ca80f386c991..d6f7dc847429 100644
--- a/src/ng/directive/ngRepeat.js
+++ b/src/ng/directive/ngRepeat.js
@@ -74,71 +74,148 @@
* For example, if an item is added to the collection, `ngRepeat` will know that all other items
* already have DOM elements, and will not re-render them.
*
- * The default tracking function (which tracks items by their identity) does not allow
- * duplicate items in arrays. This is because when there are duplicates, it is not possible
- * to maintain a one-to-one mapping between collection items and DOM elements.
- *
- * If you do need to repeat duplicate items, you can substitute the default tracking behavior
- * with your own using the `track by` expression.
- *
- * For example, you may track items by the index of each item in the collection, using the
- * special scope property `$index`:
- * ```html
- *
- * {{n}}
- *
- * ```
- *
- * You may also use arbitrary expressions in `track by`, including references to custom functions
- * on the scope:
- * ```html
- *
- * {{n}}
- *
- * ```
+ * All different types of tracking functions, their syntax, and and their support for duplicate
+ * items in collections can be found in the
+ * {@link ngRepeat#ngRepeat-arguments ngRepeat expression description}.
*
*
- * If you are working with objects that have a unique identifier property, you should track
- * by this identifier instead of the object instance. Should you reload your data later, `ngRepeat`
- * will not have to rebuild the DOM elements for items it has already rendered, even if the
- * JavaScript objects in the collection have been substituted for new ones. For large collections,
- * this significantly improves rendering performance. If you don't have a unique identifier,
- * `track by $index` can also provide a performance boost.
+ * **Best Practice:** If you are working with objects that have a unique identifier property, you
+ * should track by this identifier instead of the object instance,
+ * e.g. `item in items track by item.id`.
+ * Should you reload your data later, `ngRepeat` will not have to rebuild the DOM elements for items
+ * it has already rendered, even if the JavaScript objects in the collection have been substituted
+ * for new ones. For large collections, this significantly improves rendering performance.
*
*
- * ```html
- *
- * {{model.name}}
- *
- * ```
+ * ### Effects of DOM Element re-use
*
- *
- *
- * Avoid using `track by $index` when the repeated template contains
- * {@link guide/expression#one-time-binding one-time bindings}. In such cases, the `nth` DOM
- * element will always be matched with the `nth` item of the array, so the bindings on that element
- * will not be updated even when the corresponding item changes, essentially causing the view to get
- * out-of-sync with the underlying data.
- *
+ * When DOM elements are re-used, ngRepeat updates the scope for the element, which will
+ * automatically update any active bindings on the template. However, other
+ * functionality will not be updated, because the element is not re-created:
*
- * When no `track by` expression is provided, it is equivalent to tracking by the built-in
- * `$id` function, which tracks items by their identity:
- * ```html
- *
- * {{obj.prop}}
- *
- * ```
+ * - Directives are not re-compiled
+ * - {@link guide/expression#one-time-binding one-time expressions} on the repeated template are not
+ * updated if they have stabilized.
*
- *
- *
- * **Note:** `track by` must always be the last expression:
- *
- * ```
- *
- * {{model.name}}
- *
- * ```
+ * The above affects all kinds of element re-use due to tracking, but may be especially visible
+ * when tracking by `$index` due to the way ngRepeat re-uses elements.
*
+ * The following example shows the effects of different actions with tracking:
+
+
+
+ angular.module('ngRepeat', ['ngAnimate']).controller('repeatController', function($scope) {
+ var friends = [
+ {name:'John', age:25},
+ {name:'Mary', age:40},
+ {name:'Peter', age:85}
+ ];
+
+ $scope.removeFirst = function() {
+ $scope.friends.shift();
+ };
+
+ $scope.updateAge = function() {
+ $scope.friends.forEach(function(el) {
+ el.age = el.age + 5;
+ });
+ };
+
+ $scope.copy = function() {
+ $scope.friends = angular.copy($scope.friends);
+ };
+
+ $scope.reset = function() {
+ $scope.friends = angular.copy(friends);
+ };
+
+ $scope.reset();
+ });
+
+
+
+
+
When you click "Update Age", only the first list updates the age, because all others have
+ a one-time binding on the age property. If you then click "Copy", the current friend list
+ is copied, and now the second list updates the age, because the identity of the collection items
+ has changed and the list must be re-rendered. The 3rd and 4th list stay the same, because all the
+ items are already known according to their tracking functions.
+
+
When you click "Remove First", the 4th list has the wrong age on both remaining items. This is
+ due to tracking by $index: when the first collection item is removed, ngRepeat reuses the first
+ DOM element for the new first collection item, and so on. Since the age property is one-time
+ bound, the value remains from the collection item which was previously at this index.
+
+
+
+
+
+
+
+
+ track by $id(friend) (default):
+
+
+ {{friend.name}} is {{friend.age}} years old.
+
+
+ track by $id(friend) (default), with age one-time binding:
+
+
+ {{friend.name}} is {{::friend.age}} years old.
+
+
+ track by friend.name, with age one-time binding:
+
+
+ {{friend.name}} is {{::friend.age}} years old.
+
+
+ track by $index, with age one-time binding:
+
+
+ {{friend.name}} is {{::friend.age}} years old.
+
+
+
+
+
+ .example-animate-container {
+ background:white;
+ border:1px solid black;
+ list-style:none;
+ margin:0;
+ padding:0 10px;
+ }
+
+ .animate-repeat {
+ line-height:30px;
+ list-style:none;
+ box-sizing:border-box;
+ }
+
+ .animate-repeat.ng-move,
+ .animate-repeat.ng-enter,
+ .animate-repeat.ng-leave {
+ transition:all linear 0.5s;
+ }
+
+ .animate-repeat.ng-leave.ng-leave-active,
+ .animate-repeat.ng-move,
+ .animate-repeat.ng-enter {
+ opacity:0;
+ max-height:0;
+ }
+
+ .animate-repeat.ng-leave,
+ .animate-repeat.ng-move.ng-move-active,
+ .animate-repeat.ng-enter.ng-enter-active {
+ opacity:1;
+ max-height:30px;
+ }
+
+
+
*
* ## Special repeat start and end points
* To repeat a series of elements instead of just one parent element, ngRepeat (as well as other ng directives) supports extending
@@ -215,24 +292,38 @@
* more than one tracking expression value resolve to the same key. (This would mean that two distinct objects are
* mapped to the same DOM element, which is not possible.)
*
- *
- * Note: the `track by` expression must come last - after any filters, and the alias expression.
- *
+ * *Default tracking: $id()*: `item in items` is equivalent to `item in items track by $id(item)`.
+ * This implies that the DOM elements will be associated by item identity in the collection.
*
- * For example: `item in items` is equivalent to `item in items track by $id(item)`. This implies that the DOM elements
- * will be associated by item identity in the array.
+ * The built-in `$id()` function can be used to assign a unique
+ * `$$hashKey` property to each item in the collection. This property is then used as a key to associated DOM elements
+ * with the corresponding item in the collection by identity. Moving the same object would move
+ * the DOM element in the same way in the DOM.
+ * Note that the default id function does not support duplicate primitive values (`number`, `string`),
+ * but supports duplictae non-primitive values (`object`) that are *equal* in shape.
*
- * For example: `item in items track by $id(item)`. A built in `$id()` function can be used to assign a unique
- * `$$hashKey` property to each item in the array. This property is then used as a key to associated DOM elements
- * with the corresponding item in the array by identity. Moving the same object in array would move the DOM
- * element in the same way in the DOM.
+ * *Custom Expression*: It is possible to use any AngularJS expression to compute the tracking
+ * id, for example with a function, or using a property on the collection items.
+ * `item in items track by item.id` is a typical pattern when the items have a unique identifier,
+ * e.g. database id. In this case the object identity does not matter. Two objects are considered
+ * equivalent as long as their `id` property is same.
+ * Tracking by unique identifier is the most performant way and should be used whenever possible.
*
- * For example: `item in items track by item.id` is a typical pattern when the items come from the database. In this
- * case the object identity does not matter. Two objects are considered equivalent as long as their `id`
- * property is same.
+ * *$index*: This special property tracks the collection items by their index, and
+ * re-uses the DOM elements that match that index, e.g. `item in items track by $index`. This can
+ * be used for a performance improvement if no unique identfier is available and the identity of
+ * the collection items cannot be easily computed. It also allows duplicates.
*
- * For example: `item in items | filter:searchText track by item.id` is a pattern that might be used to apply a filter
- * to items in conjunction with a tracking expression.
+ *
+ * Note: Re-using DOM elements can have unforeseen effects. Read the
+ * {@link ngRepeat#tracking-and-duplicates section on tracking and duplicates} for
+ * more info.
+ *
+ *
+ *
+ * Note: the `track by` expression must come last - after any filters, and the alias expression:
+ * `item in items | filter:searchText as results track by item.id`
+ *
*
* * `variable in expression as alias_expression` – You can also provide an optional alias expression which will then store the
* intermediate results of the repeater after the filters have been applied. Typically this is used to render a special message
@@ -241,10 +332,10 @@
* For example: `item in items | filter:x as results` will store the fragment of the repeated items as `results`, but only after
* the items have been processed through the filter.
*
- * Please note that `as [variable name] is not an operator but rather a part of ngRepeat micro-syntax so it can be used only at the end
- * (and not as operator, inside an expression).
+ * Please note that `as [variable name] is not an operator but rather a part of ngRepeat
+ * micro-syntax so it can be used only after all filters (and not as operator, inside an expression).
*
- * For example: `item in items | filter : x | orderBy : order | limitTo : limit as results` .
+ * For example: `item in items | filter : x | orderBy : order | limitTo : limit as results track by item.id` .
*
* @example
* This example uses `ngRepeat` to display a list of people. A filter is used to restrict the displayed
@@ -255,7 +346,7 @@
I have {{friends.length}} friends. They are:
-
+
[{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old.