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 properties to ngRepeat #9795

Closed
wants to merge 1 commit into from
Closed
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
22 changes: 16 additions & 6 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 *} | value of previous element (undefined if it doesn't exist). |
* | `$next` | {@type *} | value of next element (undefined if it doesn't exist). |
*
* 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 @@ -212,7 +214,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, prevValue, nextValue) {
// TODO(perf): generate setters to shave off ~40ms or 1-1.5%
scope[valueIdentifier] = value;
if (keyIdentifier) scope[keyIdentifier] = key;
Expand All @@ -223,6 +225,8 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
// jshint bitwise: false
scope.$odd = !(scope.$even = (index&1) === 0);
// jshint bitwise: true
scope.$prev = prevValue;
scope.$next = nextValue;
};

var getBlockStart = function(block) {
Expand Down Expand Up @@ -318,7 +322,8 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
// lastBlockMap on the next iteration.
nextBlockMap = createMap(),
collectionLength,
key, value, // key/value of iteration
key, value, useCollectionKeysAsIndex, // key/value of iteration
prevValue, nextValue, // prev/next value
trackById,
trackByIdFn,
collectionKeys,
Expand Down Expand Up @@ -347,10 +352,11 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {

collectionLength = collectionKeys.length;
nextBlockOrder = new Array(collectionLength);
useCollectionKeysAsIndex = collection !== collectionKeys;

// locate existing items
for (index = 0; index < collectionLength; index++) {
key = (collection === collectionKeys) ? index : collectionKeys[index];
key = useCollectionKeysAsIndex ? collectionKeys[index] : index;
value = collection[key];
trackById = trackByIdFn(key, value, index);
if (lastBlockMap[trackById]) {
Expand Down Expand Up @@ -391,10 +397,14 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {

// we are not using forEach for perf reasons (trying to avoid #call)
for (index = 0; index < collectionLength; index++) {
key = (collection === collectionKeys) ? index : collectionKeys[index];
key = useCollectionKeysAsIndex ? collectionKeys[index] : index;
value = collection[key];
block = nextBlockOrder[index];

// assign previous and next value to local vars and pass them to scope
prevValue = index === 0 ? undefined : collection[useCollectionKeysAsIndex ? collectionKeys[index - 1] : index - 1];
nextValue = index === collectionLength ? undefined : collection[useCollectionKeysAsIndex ? collectionKeys[index + 1] : index + 1];

if (block.scope) {
// if we have already seen this object, then we need to reuse the
// associated scope/element
Expand All @@ -411,7 +421,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, prevValue, nextValue);
} else {
// new item which we don't know about
$transclude(function ngRepeatTransclude(clone, scope) {
Expand All @@ -428,7 +438,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, prevValue, nextValue);
});
}
}
Expand Down
126 changes: 126 additions & 0 deletions test/ng/directive/ngRepeatSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -713,6 +713,132 @@ describe('ngRepeat', function() {
});


it('should be able to access $prev and $next value on every iteration over an array', function() {
element = $compile(
'<ul>' +
'<li ng-repeat="item in items">{{item}}:{{$prev}}-{{$next}}|</li>' +
'</ul>')(scope);

// INIT AND FIRST CHECK
scope.items = ['banana', 'apple', 'orange'];
scope.$digest();
expect(element.text()).
toEqual('banana:-apple|apple:banana-orange|orange:apple-|');

// PUSH AND CHECK
scope.items.push('peach');
scope.$digest();
expect(element.text()).
toEqual('banana:-apple|apple:banana-orange|orange:apple-peach|peach:orange-|');

// POP'N'SHIFT AND CHECK
scope.items.pop();
scope.items.shift();
scope.$digest();
expect(element.text()).
toEqual('apple:-orange|orange:apple-|');
});


it('should be able to access $prev and $next value on every iteration over an array of objects', function() {
element = $compile(
'<ul>' +
'<li ng-repeat="item in items">' +
'{{item.name}}:' +
'<span ng-if="$prev">{{$prev.name}}</span>' +
'-' +
'<span ng-if="$next">{{$next.name}}</span>' +
'|' +
'</li>' +
'</ul>')(scope);

// INIT AND FIRST CHECK
scope.items = [{name: 'banana'}, {name: 'apple'}, {name: 'orange'}];
scope.$digest();
expect(element.text()).
toEqual('banana:-apple|apple:banana-orange|orange:apple-|');

// PUSH AND CHECK
scope.items.push({name: 'peach'});
scope.$digest();
expect(element.text()).
toEqual('banana:-apple|apple:banana-orange|orange:apple-peach|peach:orange-|');

// POP'N'SHIFT AND CHECK
scope.items.pop();
scope.items.shift();
scope.$digest();
expect(element.text()).
toEqual('apple:-orange|orange:apple-|');
});


it('should be able to access $prev and $next value on every iteration over an object', function() {
element = $compile(
'<ul>' +
'<li ng-repeat="item in items">' +
'{{item}}:' +
'<span ng-if="$prev">{{$prev}}</span>' +
'-' +
'<span ng-if="$next">{{$next}}</span>' +
'|' +
'</li>' +
'</ul>')(scope);

// INIT AND FIRST CHECK
scope.items = {first: 'great', second: 'nice', third: 'awesome'};
scope.$digest();
expect(element.text()).
toEqual('great:-nice|nice:great-awesome|awesome:nice-|');

// ADD AND CHECK
scope.items.fourth = 'marvelous';
scope.$digest();
expect(element.text()).
toEqual('great:-marvelous|marvelous:great-nice|nice:marvelous-awesome|awesome:nice-|');

// DELETE AND CHECK
delete scope.items.fourth;
delete scope.items.first;
scope.$digest();
expect(element.text()).
toEqual('nice:-awesome|awesome:nice-|');
});


it('should be able to access $prev and $next value correctly after filtering out bad keys', function() {
element = $compile(
'<ul>' +
'<li ng-repeat="item in items">' +
'{{item}}:' +
'<span ng-if="$prev">{{$prev}}</span>' +
'-' +
'<span ng-if="$next">{{$next}}</span>' +
'|' +
'</li>' +
'</ul>')(scope);

// INIT AND FIRST CHECK
scope.items = {first: 'great', second: 'nice', $toBeFilteredOut: 'bad', third: 'awesome'};
scope.$digest();
expect(element.text()).
toEqual('great:-nice|nice:great-awesome|awesome:nice-|');

// ADD AND CHECK
scope.items.fourth = 'marvelous';
scope.$digest();
expect(element.text()).
toEqual('great:-marvelous|marvelous:great-nice|nice:marvelous-awesome|awesome:nice-|');

// DELETE AND CHECK
delete scope.items.fourth;
delete scope.items.first;
scope.$digest();
expect(element.text()).
toEqual('nice:-awesome|awesome:nice-|');
});


it('should ignore $ and $$ properties', function() {
element = $compile('<ul><li ng-repeat="i in items">{{i}}|</li></ul>')(scope);
scope.items = ['a', 'b', 'c'];
Expand Down