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

fix for issue #933:ng-repeat does not work with primitive types #944

Closed
wants to merge 4 commits 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
10 changes: 10 additions & 0 deletions src/apis.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,5 +97,15 @@ HashQueueMap.prototype = {
return array.shift();
}
}
},

/**
* return the first item without deleting it
*/
peek: function(key) {
var array = this[key = hashKey(key)];
if (array) {
return array[0];
}
}
};
28 changes: 27 additions & 1 deletion src/ng/directive/ngRepeat.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ var ngRepeatDirective = ngDirective({
// We need an array of these objects since the same object can be returned from the iterator.
// We expect this to be a rare case.
var lastOrder = new HashQueueMap();
var indexValues = [];
scope.$watch(function(scope){
var index, length,
collection = scope.$eval(rhs),
Expand Down Expand Up @@ -118,7 +119,20 @@ var ngRepeatDirective = ngDirective({
for (index = 0, length = array.length; index < length; index++) {
key = (collection === array) ? index : array[index];
value = collection[key];
last = lastOrder.shift(value);

// if collection is array and value is object, it can be shifted to allow for position change
// if collection is array and value is not object, need to first check whether index is same to
// avoid shifting wrong value
// if collection is not array, need to always check index to avoid shifting wrong value
if (lastOrder.peek(value)) {
last = collection === array ?
((isObject(value)) ? lastOrder.shift(value) :
(index === lastOrder.peek(value).index ? lastOrder.shift(value) : undefined)) :
(index === lastOrder.peek(value).index ? lastOrder.shift(value) : undefined);
} else {
last = undefined;
}

if (last) {
// if we have already seen this object, then we need to reuse the
// associated scope/element
Expand All @@ -138,6 +152,12 @@ var ngRepeatDirective = ngDirective({
cursor = last.element;
}
} else {
if (indexValues.hasOwnProperty(index) && collection !== array) {
var preValue = indexValues[index];
var v = lastOrder.shift(preValue);
v.element.remove();
v.scope.$destroy();
}
// new item which we don't know about
childScope = scope.$new();
}
Expand All @@ -158,10 +178,16 @@ var ngRepeatDirective = ngDirective({
index: index
};
nextOrder.push(value, last);
indexValues[index] = value;
});
}
}

var i, l;
for (i = 0, l = indexValues.length - length; i < l; i++) {
indexValues.pop();
}

//shrink children
for (key in lastOrder) {
if (lastOrder.hasOwnProperty(key)) {
Expand Down
4 changes: 4 additions & 0 deletions test/ApiSpecs.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,11 @@ describe('api', function() {
map.push('key', 'a');
map.push('key', 'b');
expect(map[hashKey('key')]).toEqual(['a', 'b']);
expect(map.peek('key')).toEqual('a');
expect(map[hashKey('key')]).toEqual(['a', 'b']);
expect(map.shift('key')).toEqual('a');
expect(map.peek('key')).toEqual('b');
expect(map[hashKey('key')]).toEqual(['b']);
expect(map.shift('key')).toEqual('b');
expect(map.shift('key')).toEqual(undefined);
expect(map[hashKey('key')]).toEqual(undefined);
Expand Down
115 changes: 115 additions & 0 deletions test/ng/directive/ngRepeatSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,89 @@ describe('ngRepeat', function() {
}));


it('should ngRepeat over array of primitive correctly', inject(function($rootScope, $compile) {
element = $compile(
'<ul>' +
'<li ng-repeat="item in items" ng-init="suffix = \';\'" ng-bind="item + suffix"></li>' +
'</ul>')($rootScope);

Array.prototype.extraProperty = "should be ignored";
// INIT
$rootScope.items = [true, true, true];
$rootScope.$digest();
expect(element.find('li').length).toEqual(3);
expect(element.text()).toEqual('true;true;true;');
delete Array.prototype.extraProperty;

$rootScope.items = [false, true, true];
$rootScope.$digest();
expect(element.find('li').length).toEqual(3);
expect(element.text()).toEqual('false;true;true;');

$rootScope.items = [false, true, false];
$rootScope.$digest();
expect(element.find('li').length).toEqual(3);
expect(element.text()).toEqual('false;true;false;');

$rootScope.items = [true];
$rootScope.$digest();
expect(element.find('li').length).toEqual(1);
expect(element.text()).toEqual('true;');

$rootScope.items = [true, true, false];
$rootScope.$digest();
expect(element.find('li').length).toEqual(3);
expect(element.text()).toEqual('true;true;false;');

$rootScope.items = [true, false, false];
$rootScope.$digest();
expect(element.find('li').length).toEqual(3);
expect(element.text()).toEqual('true;false;false;');

// string
$rootScope.items = ['a', 'a', 'a'];
$rootScope.$digest();
expect(element.find('li').length).toEqual(3);
expect(element.text()).toEqual('a;a;a;');

$rootScope.items = ['ab', 'a', 'a'];
$rootScope.$digest();
expect(element.find('li').length).toEqual(3);
expect(element.text()).toEqual('ab;a;a;');

$rootScope.items = ['test'];
$rootScope.$digest();
expect(element.find('li').length).toEqual(1);
expect(element.text()).toEqual('test;');

$rootScope.items = ['same', 'value'];
$rootScope.$digest();
expect(element.find('li').length).toEqual(2);
expect(element.text()).toEqual('same;value;');

// number
$rootScope.items = [12, 12, 12];
$rootScope.$digest();
expect(element.find('li').length).toEqual(3);
expect(element.text()).toEqual('12;12;12;');

$rootScope.items = [53, 12, 27];
$rootScope.$digest();
expect(element.find('li').length).toEqual(3);
expect(element.text()).toEqual('53;12;27;');

$rootScope.items = [89];
$rootScope.$digest();
expect(element.find('li').length).toEqual(1);
expect(element.text()).toEqual('89;');

$rootScope.items = [89, 23];
$rootScope.$digest();
expect(element.find('li').length).toEqual(2);
expect(element.text()).toEqual('89;23;');
}));


it('should ngRepeat over object', inject(function($rootScope, $compile) {
element = $compile(
'<ul>' +
Expand All @@ -47,6 +130,38 @@ describe('ngRepeat', function() {
expect(element.text()).toEqual('misko:swe;shyam:set;');
}));


it('should ngRepeat over object with primitive value correctly', inject(function($rootScope, $compile) {
element = $compile(
'<ul>' +
'<li ng-repeat="(key, value) in items" ng-bind="key + \':\' + value + \';\' "></li>' +
'</ul>')($rootScope);
$rootScope.items = {misko:'true', shyam:'true', zhenbo: 'true'};
$rootScope.$digest();
expect(element.find('li').length).toEqual(3);
expect(element.text()).toEqual('misko:true;shyam:true;zhenbo:true;');

$rootScope.items = {misko:'false', shyam:'true', zhenbo: 'true'};
$rootScope.$digest();
expect(element.find('li').length).toEqual(3);
expect(element.text()).toEqual('misko:false;shyam:true;zhenbo:true;');

$rootScope.items = {misko:'false', shyam:'false', zhenbo: 'false'};
$rootScope.$digest();
expect(element.find('li').length).toEqual(3);
expect(element.text()).toEqual('misko:false;shyam:false;zhenbo:false;');

$rootScope.items = {misko:'true'};
$rootScope.$digest();
expect(element.find('li').length).toEqual(1);
expect(element.text()).toEqual('misko:true;');

$rootScope.items = {shyam:'true', zhenbo: 'false'};
$rootScope.$digest();
expect(element.find('li').length).toEqual(2);
expect(element.text()).toEqual('shyam:true;zhenbo:false;');
}));


it('should not ngRepeat over parent properties', inject(function($rootScope, $compile) {
var Class = function() {};
Expand Down