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

Commit 624fd80

Browse files
committed
feat(ngRepeat): use block separator comments
Closes angular#3104
1 parent 6c59e77 commit 624fd80

File tree

4 files changed

+151
-28
lines changed

4 files changed

+151
-28
lines changed

src/ng/directive/ngRepeat.js

+18-3
Original file line numberDiff line numberDiff line change
@@ -327,8 +327,21 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
327327
for (key in lastBlockMap) {
328328
if (lastBlockMap.hasOwnProperty(key)) {
329329
block = lastBlockMap[key];
330-
$animate.leave(block.elements);
331-
forEach(block.elements, function(element) { element[NG_REMOVED] = true});
330+
331+
var elementsToRemove = [];
332+
var elementToRemove = block.startNode;
333+
elementsToRemove.push(elementToRemove);
334+
335+
if (block.startNode !== block.endNode) {
336+
do {
337+
elementToRemove = elementToRemove.nextSibling;
338+
if (!elementToRemove) break;
339+
elementsToRemove.push(elementToRemove);
340+
} while (elementToRemove !== block.endNode);
341+
}
342+
343+
$animate.leave(angular.element(elementsToRemove));
344+
forEach(elementsToRemove, function(element) { element[NG_REMOVED] = true; });
332345
block.scope.$destroy();
333346
}
334347
}
@@ -338,6 +351,7 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
338351
key = (collection === collectionKeys) ? index : collectionKeys[index];
339352
value = collection[key];
340353
block = nextBlockOrder[index];
354+
if (nextBlockOrder[index - 1]) previousNode = nextBlockOrder[index - 1].endNode;
341355

342356
if (block.startNode) {
343357
// if we have already seen this object, then we need to reuse the
@@ -371,10 +385,11 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
371385

372386
if (!block.startNode) {
373387
linker(childScope, function(clone) {
388+
clone[clone.length++] = document.createComment(' end ngRepeat: ' + expression + ' ');
374389
$animate.enter(clone, null, jqLite(previousNode));
375390
previousNode = clone;
376391
block.scope = childScope;
377-
block.startNode = clone[0];
392+
block.startNode = previousNode && previousNode.endNode ? previousNode.endNode : clone[0];
378393
block.elements = clone;
379394
block.endNode = clone[clone.length - 1];
380395
nextBlockMap[block.id] = block;

test/BinderSpec.js

+23-4
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,9 @@ describe('Binder', function() {
9696
'<ul>' +
9797
'<!-- ngRepeat: item in model.items -->' +
9898
'<li ng-bind="item.a" ng-repeat="item in model.items">A</li>' +
99+
'<!-- end ngRepeat: item in model.items -->' +
99100
'<li ng-bind="item.a" ng-repeat="item in model.items">B</li>' +
101+
'<!-- end ngRepeat: item in model.items -->' +
100102
'</ul>');
101103

102104
items.unshift({a: 'C'});
@@ -105,8 +107,11 @@ describe('Binder', function() {
105107
'<ul>' +
106108
'<!-- ngRepeat: item in model.items -->' +
107109
'<li ng-bind="item.a" ng-repeat="item in model.items">C</li>' +
110+
'<!-- end ngRepeat: item in model.items -->' +
108111
'<li ng-bind="item.a" ng-repeat="item in model.items">A</li>' +
112+
'<!-- end ngRepeat: item in model.items -->' +
109113
'<li ng-bind="item.a" ng-repeat="item in model.items">B</li>' +
114+
'<!-- end ngRepeat: item in model.items -->' +
110115
'</ul>');
111116

112117
items.shift();
@@ -115,7 +120,9 @@ describe('Binder', function() {
115120
'<ul>' +
116121
'<!-- ngRepeat: item in model.items -->' +
117122
'<li ng-bind="item.a" ng-repeat="item in model.items">A</li>' +
123+
'<!-- end ngRepeat: item in model.items -->' +
118124
'<li ng-bind="item.a" ng-repeat="item in model.items">B</li>' +
125+
'<!-- end ngRepeat: item in model.items -->' +
119126
'</ul>');
120127

121128
items.shift();
@@ -134,6 +141,7 @@ describe('Binder', function() {
134141
'<ul>' +
135142
'<!-- ngRepeat: item in model.items -->' +
136143
'<li ng-repeat="item in model.items"><span ng-bind="item.a">A</span></li>' +
144+
'<!-- end ngRepeat: item in model.items -->' +
137145
'</ul>');
138146
}));
139147

@@ -148,15 +156,15 @@ describe('Binder', function() {
148156
$rootScope.items = items;
149157

150158
$rootScope.$apply();
151-
expect(element[0].childNodes.length - 1).toEqual(0);
159+
expect(element[0].childNodes.length).toEqual(1);
152160

153161
items.name = 'misko';
154162
$rootScope.$apply();
155-
expect(element[0].childNodes.length - 1).toEqual(1);
163+
expect(element[0].childNodes.length).toEqual(3);
156164

157165
delete items.name;
158166
$rootScope.$apply();
159-
expect(element[0].childNodes.length - 1).toEqual(0);
167+
expect(element[0].childNodes.length).toEqual(1);
160168
}));
161169

162170
it('IfTextBindingThrowsErrorDecorateTheSpan', function() {
@@ -223,13 +231,19 @@ describe('Binder', function() {
223231
'<div name="a" ng-repeat="m in model">'+
224232
'<!-- ngRepeat: i in m.item -->' +
225233
'<ul name="a1" ng-repeat="i in m.item"></ul>'+
234+
'<!-- end ngRepeat: i in m.item -->' +
226235
'<ul name="a2" ng-repeat="i in m.item"></ul>'+
236+
'<!-- end ngRepeat: i in m.item -->' +
227237
'</div>'+
238+
'<!-- end ngRepeat: m in model -->' +
228239
'<div name="b" ng-repeat="m in model">'+
229240
'<!-- ngRepeat: i in m.item -->' +
230241
'<ul name="b1" ng-repeat="i in m.item"></ul>'+
242+
'<!-- end ngRepeat: i in m.item -->' +
231243
'<ul name="b2" ng-repeat="i in m.item"></ul>'+
244+
'<!-- end ngRepeat: i in m.item -->' +
232245
'</div>' +
246+
'<!-- end ngRepeat: m in model -->' +
233247
'</div>');
234248
}));
235249

@@ -306,15 +320,18 @@ describe('Binder', function() {
306320
'<div ng-repeat="i in [0,1]" ng-class-even="\'e\'" ng-class-odd="\'o\'"></div>' +
307321
'</div>')($rootScope);
308322
$rootScope.$apply();
323+
309324
var d1 = jqLite(element[0].childNodes[1]);
310-
var d2 = jqLite(element[0].childNodes[2]);
325+
var d2 = jqLite(element[0].childNodes[3]);
311326
expect(d1.hasClass('o')).toBeTruthy();
312327
expect(d2.hasClass('e')).toBeTruthy();
313328
expect(sortedHtml(element)).toBe(
314329
'<div>' +
315330
'<!-- ngRepeat: i in [0,1] -->' +
316331
'<div class="o" ng-class-even="\'e\'" ng-class-odd="\'o\'" ng-repeat="i in [0,1]"></div>' +
332+
'<!-- end ngRepeat: i in [0,1] -->' +
317333
'<div class="e" ng-class-even="\'e\'" ng-class-odd="\'o\'" ng-repeat="i in [0,1]"></div>' +
334+
'<!-- end ngRepeat: i in [0,1] -->' +
318335
'</div>');
319336
}));
320337

@@ -420,7 +437,9 @@ describe('Binder', function() {
420437
'<ul>' +
421438
'<!-- ngRepeat: (k,v) in {a:0,b:1} -->' +
422439
'<li ng-bind=\"k + v\" ng-repeat="(k,v) in {a:0,b:1}">a0</li>' +
440+
'<!-- end ngRepeat: (k,v) in {a:0,b:1} -->' +
423441
'<li ng-bind=\"k + v\" ng-repeat="(k,v) in {a:0,b:1}">b1</li>' +
442+
'<!-- end ngRepeat: (k,v) in {a:0,b:1} -->' +
424443
'</ul>');
425444
}));
426445

test/ng/directive/ngClassSpec.js

+5-5
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ describe('ngClass', function() {
166166
element = $compile('<ul><li ng-repeat="i in [0,1]" class="existing" ng-class-odd="\'odd\'" ng-class-even="\'even\'"></li><ul>')($rootScope);
167167
$rootScope.$digest();
168168
var e1 = jqLite(element[0].childNodes[1]);
169-
var e2 = jqLite(element[0].childNodes[2]);
169+
var e2 = jqLite(element[0].childNodes[3]);
170170
expect(e1.hasClass('existing')).toBeTruthy();
171171
expect(e1.hasClass('odd')).toBeTruthy();
172172
expect(e2.hasClass('existing')).toBeTruthy();
@@ -181,7 +181,7 @@ describe('ngClass', function() {
181181
'<ul>')($rootScope);
182182
$rootScope.$apply();
183183
var e1 = jqLite(element[0].childNodes[1]);
184-
var e2 = jqLite(element[0].childNodes[2]);
184+
var e2 = jqLite(element[0].childNodes[3]);
185185

186186
expect(e1.hasClass('plainClass')).toBeTruthy();
187187
expect(e1.hasClass('odd')).toBeTruthy();
@@ -199,7 +199,7 @@ describe('ngClass', function() {
199199
'<ul>')($rootScope);
200200
$rootScope.$apply();
201201
var e1 = jqLite(element[0].childNodes[1]);
202-
var e2 = jqLite(element[0].childNodes[2]);
202+
var e2 = jqLite(element[0].childNodes[3]);
203203

204204
expect(e1.hasClass('A')).toBeTruthy();
205205
expect(e1.hasClass('B')).toBeTruthy();
@@ -273,7 +273,7 @@ describe('ngClass', function() {
273273
$rootScope.$digest();
274274

275275
var e1 = jqLite(element[0].childNodes[1]);
276-
var e2 = jqLite(element[0].childNodes[2]);
276+
var e2 = jqLite(element[0].childNodes[3]);
277277

278278
expect(e1.hasClass('odd')).toBeTruthy();
279279
expect(e1.hasClass('even')).toBeFalsy();
@@ -295,7 +295,7 @@ describe('ngClass', function() {
295295
$rootScope.$digest();
296296

297297
var e1 = jqLite(element[0].childNodes[1]);
298-
var e2 = jqLite(element[0].childNodes[2]);
298+
var e2 = jqLite(element[0].childNodes[3]);
299299

300300
expect(e1.hasClass('odd')).toBeTruthy();
301301
expect(e1.hasClass('even')).toBeFalsy();

test/ng/directive/ngRepeatSpec.js

+105-16
Original file line numberDiff line numberDiff line change
@@ -613,7 +613,9 @@ describe('ngRepeat', function() {
613613
'<div>' +
614614
'<!-- ngRepeat: i in items -->' +
615615
'<div ng-repeat="i in items" rr="">1|</div>' +
616+
'<!-- end ngRepeat: i in items -->' +
616617
'<div ng-repeat="i in items" rr="">2|</div>' +
618+
'<!-- end ngRepeat: i in items -->' +
617619
'</div>'
618620
);
619621
}));
@@ -643,7 +645,9 @@ describe('ngRepeat', function() {
643645
'<div>' +
644646
'<!-- ngRepeat: i in items -->' +
645647
'<div ng-repeat="i in items" rr="">1|</div>' +
648+
'<!-- end ngRepeat: i in items -->' +
646649
'<div ng-repeat="i in items" rr="">2|</div>' +
650+
'<!-- end ngRepeat: i in items -->' +
647651
'</div>'
648652
);
649653
}));
@@ -740,6 +744,66 @@ describe('ngRepeat', function() {
740744
});
741745

742746

747+
it('should add separator comments after each item', inject(function ($compile, $rootScope) {
748+
var check = function () {
749+
var children = element.find('div');
750+
expect(children.length).toBe(3);
751+
752+
// Note: COMMENT_NODE === 8
753+
expect(children[0].nextSibling.nodeType).toBe(8);
754+
expect(children[0].nextSibling.nodeValue).toBe(' end ngRepeat: val in values ');
755+
expect(children[1].nextSibling.nodeType).toBe(8);
756+
expect(children[1].nextSibling.nodeValue).toBe(' end ngRepeat: val in values ');
757+
expect(children[2].nextSibling.nodeType).toBe(8);
758+
expect(children[2].nextSibling.nodeValue).toBe(' end ngRepeat: val in values ');
759+
}
760+
761+
$rootScope.values = [1, 2, 3];
762+
763+
element = $compile(
764+
'<div>' +
765+
'<div ng-repeat="val in values">val:{{val}};</div>' +
766+
'</div>'
767+
)($rootScope);
768+
769+
$rootScope.$digest();
770+
check();
771+
772+
$rootScope.values.shift();
773+
$rootScope.values.push(4);
774+
$rootScope.$digest();
775+
check();
776+
}));
777+
778+
779+
it('should remove whole block even if the number of elements inside it changes', inject(
780+
function ($compile, $rootScope) {
781+
782+
$rootScope.values = [1, 2, 3];
783+
784+
element = $compile(
785+
'<div>' +
786+
'<div ng-repeat-start="val in values"></div>' +
787+
'<span>{{val}}</span>' +
788+
'<p ng-repeat-end></p>' +
789+
'</div>'
790+
)($rootScope);
791+
792+
$rootScope.$digest();
793+
794+
var ends = element.find('p');
795+
expect(ends.length).toBe(3);
796+
797+
// insert an extra element inside the second block
798+
var extra = angular.element('<strong></strong>')[0];
799+
element[0].insertBefore(extra, ends[1]);
800+
801+
$rootScope.values.splice(1, 1);
802+
$rootScope.$digest();
803+
expect(element.find('strong').length).toBe(0);
804+
}));
805+
806+
743807
describe('stability', function() {
744808
var a, b, c, d, lis;
745809

@@ -843,28 +907,53 @@ describe('ngRepeat', function() {
843907
});
844908
});
845909

846-
it('should grow multi-node repeater', inject(function($compile, $rootScope) {
847-
$rootScope.show = false;
848-
$rootScope.books = [
849-
{title:'T1', description: 'D1'},
850-
{title:'T2', description: 'D2'}
851-
];
852-
element = $compile(
910+
911+
describe('ngRepeatStart', function () {
912+
it('should grow multi-node repeater', inject(function($compile, $rootScope) {
913+
$rootScope.show = false;
914+
$rootScope.books = [
915+
{title:'T1', description: 'D1'},
916+
{title:'T2', description: 'D2'}
917+
];
918+
element = $compile(
919+
'<div>' +
920+
'<dt ng-repeat-start="book in books">{{book.title}}:</dt>' +
921+
'<dd ng-repeat-end>{{book.description}};</dd>' +
922+
'</div>')($rootScope);
923+
924+
$rootScope.$digest();
925+
expect(element.text()).toEqual('T1:D1;T2:D2;');
926+
$rootScope.books.push({title:'T3', description: 'D3'});
927+
$rootScope.$digest();
928+
expect(element.text()).toEqual('T1:D1;T2:D2;T3:D3;');
929+
}));
930+
931+
932+
it('should not clobber ng-if when updating collection', inject(function ($compile, $rootScope) {
933+
$rootScope.values = [1, 2, 3];
934+
$rootScope.showMe = true;
935+
936+
element = $compile(
853937
'<div>' +
854-
'<dt ng-repeat-start="book in books">{{book.title}}:</dt>' +
855-
'<dd ng-repeat-end>{{book.description}};</dd>' +
856-
'</div>')($rootScope);
938+
'<div ng-repeat-start="val in values">val:{{val}};</div>' +
939+
'<div ng-if="showMe" ng-repeat-end>if:{{val}};</div>' +
940+
'</div>'
941+
)($rootScope);
857942

858-
$rootScope.$digest();
859-
expect(element.text()).toEqual('T1:D1;T2:D2;');
860-
$rootScope.books.push({title:'T3', description: 'D3'});
861-
$rootScope.$digest();
862-
expect(element.text()).toEqual('T1:D1;T2:D2;T3:D3;');
863-
}));
943+
$rootScope.$digest();
944+
expect(element.find('div').length).toBe(6);
864945

946+
$rootScope.values.shift();
947+
$rootScope.values.push(4);
865948

949+
$rootScope.$digest();
950+
expect(element.find('div').length).toBe(6);
951+
expect(element.text()).not.toContain('if:1;');
952+
}));
953+
});
866954
});
867955

956+
868957
describe('ngRepeat animations', function() {
869958
var body, element, $rootElement;
870959

0 commit comments

Comments
 (0)