From 60c31e074b1d9a23e90dca326f274c6789ecb578 Mon Sep 17 00:00:00 2001 From: "R. Merkert" Date: Wed, 24 Apr 2013 22:17:14 -0400 Subject: [PATCH] Allow duplicate objects in ngRepeat. Currently, ngRepeat doesn't allow modification of a list that contains duplicates. For example, while ng-repeat='i in [1,1,1]' is allowed, it is not possible to add a 1 to the list after it has been iterated at least once. This patch addresses this issue. Just by observing a change from [1,1,1] to [1,1,1,1] it is not possible to know the position where the additional 1 was inserted (vice versa for removal of 1). For the purposes of this patch, we assume that the item was inserted after all other duplicates (or is the last duplicate to be removed). This patch does have a visible effect when using animations. Consider the following list [a,b,a] and inserting a in position 0 to yield [a,a,b,a]. Since we're assuming that a was added, we need to apply a series of moves to yield the desired list: * a in position 0 does not change * a moves from position 2 to position 1 * b moves from position 1 to position 2 * a new a is added in position 3 It is probably possible to minimize the number of moves necessary, but that would come at some cost. This patch assumes that no two objects that are not occupying the same memory location will have the same $$hashKey (which is the same assumption of the previous implementation but it was not stated!) --- src/ng/directive/ngRepeat.js | 38 +++++++-------- test/ng/directive/ngRepeatSpec.js | 77 ++++++++++++++++--------------- 2 files changed, 59 insertions(+), 56 deletions(-) diff --git a/src/ng/directive/ngRepeat.js b/src/ng/directive/ngRepeat.js index a7d558957d2d..363b01fac9d1 100644 --- a/src/ng/directive/ngRepeat.js +++ b/src/ng/directive/ngRepeat.js @@ -194,7 +194,7 @@ var ngRepeatDirective = ['$parse', '$animator', function($parse, $animator) { // - element: previous element. // - index: position var lastBlockMap = {}; - + //watch props $scope.$watchCollection(rhs, function ngRepeatAction(collection){ var index, length, @@ -233,33 +233,35 @@ var ngRepeatDirective = ['$parse', '$animator', function($parse, $animator) { key = (collection === collectionKeys) ? index : collectionKeys[index]; value = collection[key]; trackById = trackByIdFn(key, value, index); + // FIXME: should we check that two objects with the same trackById are the same objects + if (!nextBlockMap.hasOwnProperty(trackById)) { + nextBlockMap[trackById] = []; + } if(lastBlockMap.hasOwnProperty(trackById)) { - block = lastBlockMap[trackById] - delete lastBlockMap[trackById]; - nextBlockMap[trackById] = block; + if (lastBlockMap[trackById].length===1) { + block = lastBlockMap[trackById][0]; + delete lastBlockMap[trackById]; + } + else { + block = lastBlockMap[trackById].splice(0,1)[0]; + } + nextBlockMap[trackById].push(block); nextBlockOrder[index] = block; - } else if (nextBlockMap.hasOwnProperty(trackById)) { - // restore lastBlockMap - forEach(nextBlockOrder, function(block) { - if (block && block.element) lastBlockMap[block.id] = block; - }); - // This is a duplicate and we need to throw an error - throw new Error('Duplicates in a repeater are not allowed. Repeater: ' + expression + - ' key: ' + trackById); } else { // new never before seen block nextBlockOrder[index] = { id: trackById }; - nextBlockMap[trackById] = false; } } // remove existing items for (key in lastBlockMap) { if (lastBlockMap.hasOwnProperty(key)) { - block = lastBlockMap[key]; - animate.leave(block.element); - block.element[0][NG_REMOVED] = true; - block.scope.$destroy(); + for (index =0;index