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

Commit

Permalink
fix(ngRepeat): expose $first, $middle and $last instead of $position
Browse files Browse the repository at this point in the history
$position marker doesn't work well in cases when we have just one item
in the list because then the item is both the first and last. To solve
this properly we need to expose individual $first and $middle and $last
flags.

BREAKING CHANGE: $position is not exposed in repeater scopes any more

To update, search for $position and replace it with one of $first,
$middle or $last.

Closes #912
  • Loading branch information
maxmart authored and IgorMinar committed May 22, 2012
1 parent 84542d2 commit 1d38867
Show file tree
Hide file tree
Showing 2 changed files with 34 additions and 18 deletions.
14 changes: 7 additions & 7 deletions src/ng/directive/ngRepeat.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,9 @@
* Special properties are exposed on the local scope of each template instance, including:
*
* * `$index` – `{number}` – iterator offset of the repeated element (0..length-1)
* * `$position` – `{string}` – position of the repeated element in the iterator. One of:
* * `'first'`,
* * `'middle'`
* * `'last'`
* * `$first` – `{boolean}` – true if the repeated element is first in the iterator.
* * `$middle` – `{boolean}` – true if the repeated element is between the first and last in the iterator.
* * `$last` – `{boolean}` – true if the repeated element is last in the iterator.
*
*
* @element ANY
Expand Down Expand Up @@ -145,9 +144,10 @@ var ngRepeatDirective = ngDirective({
childScope[valueIdent] = value;
if (keyIdent) childScope[keyIdent] = key;
childScope.$index = index;
childScope.$position = index === 0 ?
'first' :
(index == collectionLength - 1 ? 'last' : 'middle');

childScope.$first = (index === 0);
childScope.$last = (index === (collectionLength - 1));
childScope.$middle = !(childScope.$first || childScope.$last);

if (!last) {
linker(childScope, function(clone){
Expand Down
38 changes: 27 additions & 11 deletions test/ng/directive/ngRepeatSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,42 +104,58 @@ describe('ngRepeat', function() {
}));


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

$rootScope.items.push('frodo');
$rootScope.$digest();
expect(element.text()).toEqual('misko:first|shyam:middle|doug:middle|frodo:last|');
expect(element.text()).
toEqual('misko:true-false-false|' +
'shyam:false-true-false|' +
'doug:false-true-false|' +
'frodo:false-false-true|');

$rootScope.items.pop();
$rootScope.items.pop();
$rootScope.$digest();
expect(element.text()).toEqual('misko:first|shyam:last|');
expect(element.text()).toEqual('misko:true-false-false|shyam:false-false-true|');

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


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

delete $rootScope.items.doug;
delete $rootScope.items.frodo;
$rootScope.$digest();
expect(element.text()).toEqual('misko:m:first|shyam:s:last|');
expect(element.text()).toEqual('misko:m:true-false-false|shyam:s:false-false-true|');

delete $rootScope.items.shyam;
$rootScope.$digest();
expect(element.text()).toEqual('misko:m:true-false-true|');
}));


Expand Down Expand Up @@ -207,7 +223,7 @@ describe('ngRepeat', function() {
beforeEach(inject(function($rootScope, $compile) {
element = $compile(
'<ul>' +
'<li ng-repeat="item in items" ng-bind="key + \':\' + val + \':\' + $position + \'|\'"></li>' +
'<li ng-repeat="item in items">{{key}}:{{val}}|></li>' +
'</ul>')($rootScope);
a = {};
b = {};
Expand Down

2 comments on commit 1d38867

@coli
Copy link

@coli coli commented on 1d38867 May 23, 2012

Choose a reason for hiding this comment

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

would it make sense to namespace all 4 of the variables? like $repeatParam={
index:"",
first:true,
middle:true,
last:true
}

@maxmart
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Well, that might be a bit too verbose. Compare
<li ng-class="{first: $repeatParam.first, last: $repeatParam.last}"></li>
to
<li ng-class="{first: $first, last: $last}"></li>

Although the namespace could be shorter, like $ng.first or $repeat.first, I think that just having the dollar sign prefix in front of first/middle/last/index is enough to not accidentally cause a conflict with any of the user's own variables.

Please sign in to comment.