I dissected ng-repeat and extracted the code blocks attached, seeing that these comprise the logic that handles the repeating algorithm (which I want to understand how it works).
I have quite a few questions, but since they are all about the internals of ng-repeat I chose to ask them all here. I don't see any reason to separate them into different SO questions. I have marked inline to which line(s) of code each question refers to.
- Why do they need to make sure that
trackById
is not the native hasOwnProperty
function? (that's what that assertNotHasOwnProperty
function does, part of Angular's internal API)
- As far as my intuition go, this code executes on items already in the repeater, when it has to update the collection - it just picks them up and pushes them into the list for processing, right?
- This code block obviously looks for duplicates in the repeater collection. But how exactly does it do that is beyond me. Please explain.
- Why does Angular have to store the block object both
nextBlockMap
and in nextBlockOrder
?
- What are
block.endNode
and block.startNode
?
- I assume the answer to the above question will clarify how this algorithm work, but please explain why it has to check if the
nextNode
has (been) '$$NG_REMOVED'
?
- What happens here? Again, I assume question 6 will already provide an answer to this one. But still pointing that out.
Like I said, I dug through ng-repeat to find the code I deemed relevant to the repeating mechanism. Plus, I do understand the rest of the directive. So without further ado, here is the code (from v1.2.0):
length = nextBlockOrder.length = collectionKeys.length;
for (index = 0; index < length; index++) {
key = (collection === collectionKeys) ? index : collectionKeys[index];
value = collection[key];
trackById = trackByIdFn(key, value, index);
// question #1
assertNotHasOwnProperty(trackById, '`track by` id');
// question #2
if (lastBlockMap.hasOwnProperty(trackById)) {
block = lastBlockMap[trackById];
delete lastBlockMap[trackById];
nextBlockMap[trackById] = block;
nextBlockOrder[index] = block;
// question #3
} else if (nextBlockMap.hasOwnProperty(trackById)) {
// restore lastBlockMap
forEach(nextBlockOrder, function(block) {
if (block && block.startNode) lastBlockMap[block.id] = block;
});
// This is a duplicate and we need to throw an error
throw ngRepeatMinErr('dupes', "Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: {0}, Duplicate key: {1}",
expression, trackById);
// question #4
} else {
// new never before seen block
nextBlockOrder[index] = { id: trackById };
nextBlockMap[trackById] = false;
}
}
for (index = 0, length = collectionKeys.length; index < length; index++) {
key = (collection === collectionKeys) ? index : collectionKeys[index];
value = collection[key];
block = nextBlockOrder[index];
// question #5
if (nextBlockOrder[index - 1]) previousNode = nextBlockOrder[index - 1].endNode;
if (block.startNode) {
// if we have already seen this object, then we need to reuse the
// associated scope/element
childScope = block.scope;
// question #6
nextNode = previousNode;
do {
nextNode = nextNode.nextSibling;
} while(nextNode && nextNode[NG_REMOVED]);
if (block.startNode != nextNode) {
// existing item which got moved
$animate.move(getBlockElements(block), null, jqLite(previousNode));
}
previousNode = block.endNode;
} else {
// new item which we don't know about
childScope = $scope.$new();
}
// question #7
if (!block.startNode) {
linker(childScope, function(clone) {
clone[clone.length++] = document.createComment(' end ngRepeat: ' + expression + ' ');
$animate.enter(clone, null, jqLite(previousNode));
previousNode = clone;
block.scope = childScope;
block.startNode = previousNode && previousNode.endNode ? previousNode.endNode : clone[0];
block.endNode = clone[clone.length - 1];
nextBlockMap[block.id] = block;
});
}
}
lastBlockMap = nextBlockMap;
See Question&Answers more detail:
os 与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…