Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

feat(ngRepeat): add $prev and $next #11948

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions src/ng/directive/ngRepeat.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
* | `$last` | {@type boolean} | true if the repeated element is last in the iterator. |
* | `$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). |
* | `$prev` | {@type number} {@type string} {@type null} | previous index/key of the iterator position `$index` (null if `$index` is `$first`). |
* | `$next` | {@type number} {@type string} {@type null} | next index/key of the iterator position `$index` (null if `$index` is `$last`). |
*
* Creating aliases for these properties is possible with {@link ng.directive:ngInit `ngInit`}.
* This may be useful when, for instance, nesting ngRepeats.
Expand Down Expand Up @@ -291,7 +293,7 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
var NG_REMOVED = '$$NG_REMOVED';
var ngRepeatMinErr = minErr('ngRepeat');

var updateScope = function(scope, index, valueIdentifier, value, keyIdentifier, key, arrayLength) {
var updateScope = function(scope, index, valueIdentifier, value, keyIdentifier, key, arrayLength, prev, next) {
// TODO(perf): generate setters to shave off ~40ms or 1-1.5%
scope[valueIdentifier] = value;
if (keyIdentifier) scope[keyIdentifier] = key;
Expand All @@ -302,6 +304,8 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
// jshint bitwise: false
scope.$odd = !(scope.$even = (index&1) === 0);
// jshint bitwise: true
scope.$prev = prev;
scope.$next = next;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, I can understand why you'd want this feature, but we do see a real performance impact from adding properties to each scope, it's not ideal. I think we need to be very careful about adding new ones

};

var getBlockStart = function(block) {
Expand Down Expand Up @@ -398,6 +402,7 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
nextBlockMap = createMap(),
collectionLength,
key, value, // key/value of iteration
prev, next,
trackById,
trackByIdFn,
collectionKeys,
Expand Down Expand Up @@ -471,6 +476,8 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
for (index = 0; index < collectionLength; index++) {
key = (collection === collectionKeys) ? index : collectionKeys[index];
value = collection[key];
prev = (index > 0 ? ((collection === collectionKeys) ? (index - 1) : collectionKeys[index - 1]) : null);
next = (index < collectionLength - 1 ? ((collection === collectionKeys) ? (index + 1) : collectionKeys[index + 1]) : null);
block = nextBlockOrder[index];

if (block.scope) {
Expand All @@ -489,7 +496,7 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
$animate.move(getBlockNodes(block.clone), null, jqLite(previousNode));
}
previousNode = getBlockEnd(block);
updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength);
updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength, prev, next);
} else {
// new item which we don't know about
$transclude(function ngRepeatTransclude(clone, scope) {
Expand All @@ -506,7 +513,7 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
// by a directive with templateUrl when its template arrives.
block.clone = clone;
nextBlockMap[block.id] = block;
updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength);
updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength, prev, next);
});
}
}
Expand Down
51 changes: 50 additions & 1 deletion test/ng/directive/ngRepeatSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,8 @@ describe('ngRepeat', function() {
'$last',
'$even',
'$odd',
'$prev',
'$next',
'obj[key]',
'obj["key"]',
'obj[\'key\']',
Expand Down Expand Up @@ -673,7 +675,6 @@ describe('ngRepeat', function() {
expect(element.text()).toEqual('misko:m:true-false-true|');
});


it('should expose iterator position as $even and $odd when iterating over objects',
function() {
element = $compile(
Expand All @@ -694,6 +695,54 @@ describe('ngRepeat', function() {
expect(element.text()).toBe('misko:m:true-false|doug:d:false-true|');
});

it('should expose iterator position as $first, $prev, $next and $last when iterating over arrays',
function() {
element = $compile(
'<ul>' +
'<li ng-repeat="item in items">{{item}}:{{$first}}-{{$prev}}-{{$next}}-{{$last}}|</li>' +
'</ul>')(scope);
scope.items = ['misko', 'shyam', 'doug'];
scope.$digest();
expect(element.text()).
toEqual('misko:true--1-false|shyam:false-0-2-false|doug:false-1--true|');

scope.items.push('frodo');
scope.$digest();
expect(element.text()).
toEqual('misko:true--1-false|' +
'shyam:false-0-2-false|' +
'doug:false-1-3-false|' +
'frodo:false-2--true|');

scope.items.pop();
scope.items.pop();
scope.$digest();
expect(element.text()).toEqual('misko:true--1-false|shyam:false-0--true|');

scope.items.pop();
scope.$digest();
expect(element.text()).toEqual('misko:true---true|');
});

it('should expose iterator position as $first, $prev, $next and $last when iterating over objects',
function() {
element = $compile(
'<ul>' +
'<li ng-repeat="(key, val) in items">{{key}}:{{val}}:{{$first}}-{{$prev}}-{{$next}}-{{$last}}|</li>' +
'</ul>')(scope);
scope.items = {'misko':'m', 'shyam':'s', 'doug':'d'};
scope.$digest();
expect(element.text()).
toEqual('misko:m:true--shyam-false|' +
'shyam:s:false-misko-doug-false|' +
'doug:d:false-shyam--true|');
delete scope.items.doug;
scope.$digest();
expect(element.text()).toEqual('misko:m:true--shyam-false|shyam:s:false-misko--true|');
delete scope.items.shyam;
scope.$digest();
expect(element.text()).toEqual('misko:m:true---true|');
});

it('should calculate $first, $middle and $last when we filter out properties from an obj', function() {
element = $compile(
Expand Down