diff --git a/src/ng/directive/ngRepeat.js b/src/ng/directive/ngRepeat.js index f3a5ca7d8278..63fb5ff985d3 100644 --- a/src/ng/directive/ngRepeat.js +++ b/src/ng/directive/ngRepeat.js @@ -20,7 +20,7 @@ * | `$even` | {@type boolean} | true if the iterator position `$index` is even (otherwise false). | * | `$odd` | {@type boolean} | true if the iterator position `$index` is odd (otherwise false). | * - * Creating aliases for these properties is possible with {@link ng.directive:ngInit `ngInit`}. + * Creating an alternative name for the property `$index` is possible with the `index as` syntax. * This may be useful when, for instance, nesting ngRepeats. * * # Special repeat start and end points @@ -90,6 +90,15 @@ * * For example: `(name, age) in {'adam':10, 'amalie':12}`. * + * * `variable in expression index as property_name` - You can also provide an optional property name to be used + * as the index. If no property name is specified the default property name `$index` is used. + * + * For example: `item in items` is equivalent to `item in items index as $index`. This implies that the + * property `$index` is used to specify the index. + * + * For example: `item in items index as myIndex` can be used when there is a need to keep the index property + * into a user defined property name. This is useful when dealing with nested ng-repeats. + * * * `variable in expression track by tracking_expression` – You can also provide an optional tracking function * which can be used to associate the objects in the collection with the DOM elements. If no tracking function * is specified the ng-repeat associates elements by identity in the collection. It is an error to have @@ -208,19 +217,20 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) { $$tlb: true, link: function($scope, $element, $attr, ctrl, $transclude){ var expression = $attr.ngRepeat; - var match = expression.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?\s*$/), - trackByExp, trackByExpGetter, trackByIdExpFn, trackByIdArrayFn, trackByIdObjFn, + var match = expression.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+index\s+as\s+([\s\S]+?))?(?:\s+track\s+by\s+([\s\S]+?))?\s*$/), + indexBy, trackByExp, trackByExpGetter, trackByIdExpFn, trackByIdArrayFn, trackByIdObjFn, lhs, rhs, valueIdentifier, keyIdentifier, hashFnLocals = {$id: hashKey}; if (!match) { - throw ngRepeatMinErr('iexp', "Expected expression in form of '_item_ in _collection_[ track by _id_]' but got '{0}'.", + throw ngRepeatMinErr('iexp', "Expected expression in form of '_item_ in _collection_[ index as _property_][ track by _id_]' but got '{0}'.", expression); } lhs = match[1]; rhs = match[2]; - trackByExp = match[3]; + indexBy = match[3] || '$index'; + trackByExp = match[4]; if (trackByExp) { trackByExpGetter = $parse(trackByExp); @@ -275,7 +285,7 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) { var updateScope = function(scope, index) { scope[valueIdentifier] = value; if (keyIdentifier) scope[keyIdentifier] = key; - scope.$index = index; + scope[indexBy] = index; scope.$first = (index === 0); scope.$last = (index === (arrayLength - 1)); scope.$middle = !(scope.$first || scope.$last); diff --git a/test/ng/directive/ngRepeatSpec.js b/test/ng/directive/ngRepeatSpec.js index 9be6ec667e61..2338ca50753b 100644 --- a/test/ng/directive/ngRepeatSpec.js +++ b/test/ng/directive/ngRepeatSpec.js @@ -427,7 +427,7 @@ describe('ngRepeat', function() { element = jqLite(''); $compile(element)(scope); expect($exceptionHandler.errors.shift()[0].message). - toMatch(/^\[ngRepeat:iexp\] Expected expression in form of '_item_ in _collection_\[ track by _id_\]' but got 'i dont parse'\./); + toMatch(/^\[ngRepeat:iexp\] Expected expression in form of '_item_ in _collection_\[ index as _property_\]\[ track by _id_\]' but got 'i dont parse'\./); }); @@ -461,6 +461,29 @@ describe('ngRepeat', function() { }); + it('should be possible to define the `$index` property when iterating over arrays', + function() { + element = $compile( + '')(scope); + scope.items = ['misko', 'shyam', 'frodo']; + scope.$digest(); + expect(element.text()).toEqual('misko:0|shyam:1|frodo:2|'); + }); + + it('should be possible to define the `$index` property when iterating over arrays', + function() { + element = $compile( + '')(scope); + scope.items = {'misko':'m', 'shyam':'s', 'frodo':'f'}; + scope.$digest(); + expect(element.text()).toEqual('frodo:f:0|misko:m:1|shyam:s:2|'); + }); + + it('should expose iterator position as $first, $middle and $last when iterating over arrays', function() { element = $compile( @@ -618,6 +641,20 @@ describe('ngRepeat', function() { }); + it('should be possible to use `index as` in nested arrays', function() { + element = $compile( + '')(scope); + scope.items = [0, 1, 2]; + scope.$digest(); + + expect(element.text()).toEqual('0:0|0:1|0:2|X1:0|1:1|1:2|X2:0|2:1|2:2|X'); + }); + + it('should ignore non-array element properties when iterating over an array', function() { element = $compile('')(scope); scope.array = ['a', 'b', 'c'];