How does ng-repeat work?

I parsed ng-repeat and extracted the attached blocks of code, seeing that they contain logic that processes the repeating algorithm (which I want to understand how it works).

I have a few questions, but since they are all about the ng-repeat internal elements, I decided to ask them all here. I see no reason to separate them into different SO questions. I am marked inline, to which the line (s) of code relates, to which each question relates.

  • Why do they need to make sure trackById not a native hasOwnProperty function? (something that this function does is assertNotHasOwnProperty , part of the Angular internal API)
  • As for my intuition, this code is executed on elements that are already in the repeater, when it should update the collection - it just picks them up and pushes them into the list for processing, right?
  • This code block is obviously looking for duplicates in the repeater collection. But how exactly this happens is beyond me. Please explain.
  • Why should Angular store the block object as nextBlockMap and in nextBlockOrder ?
  • What are block.endNode and block.startNode ?
  • I suppose the answer to this question will explain how this algorithm works, but please explain why it should check if nextNode (was) '$$NG_REMOVED' ?
  • What's going on here? Again, I believe that question 6 will already provide an answer to this question. But still pointing to it.

As I said, I broke through ng-repeat to find code that I find relevant to the repeating mechanism. In addition, I understand the rest of the directive. So, without further ado, here is the code (from version 2.1.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; 
+8
angularjs angularjs-ng-repeat
source share
1 answer

After some refusal to the directive, I got acquainted with the ng-repeater code and was able to answer some of my questions. I highlighted in bold what I still could not understand on my own, and I would be grateful if someone could shed light on the bold parts:

  • The identifier is checked for hasOwnProperty because it uses this method to check for the identifier in the iteration objects ( lastBlockMap , nextBlockMap ) (this process is explained below). I could not find out which scenario could happen, however.
  • I was right in my assumption. nextBlockMap contains all the elements that will be transferred when the current model changes. lastBlockMap contains everything from the previous model update. It is used to find duplicates in a collection.
  • Well, actually it's pretty simple. In this for ng-repeat nextBlockMap populated nextBlockMap elements from lastBlockMap . Looking at the order of if s, it is easy to see that if an element cannot be found in lastBlockMap , but it is already present in nextBlockMap (that is, it was already copied there from lastBlockMap , and therefore its trackById appears twice in the collection) is a duplicate. What forEach does is simply executing through all the initialized elements in nextBlockMap ( block that have the startNode property) and return their identifier in lastBlockMap . I cannot understand why this is necessary.
  • The only reason I can find to split nextBlockOrder (all trackById in the array) from nextBlockMap (all block objects in the trackById hash) is this line, which works with the array simplifies and simplifies the work: if (nextBlockOrder[index - 1]) previousNode = nextBlockOrder[index - 1].endNode; . This is explained in the answers to questions 5 and 6:
  • block.startNode and block.endNode are the first and last DOM nodes in the block belonging to the element in the assembled repetition. Therefore, this line here sets the previousNode to refer to the last DOM node of the previous element in the relay.
  • previousNode then used as the first node in the loop, which checks how the DOM changed when elements were moved or removed from the repeater collection - again, only if we are not working with the first block in the array.
  • It's easy - it initializes the block - assigns $scope and startNode and endNode for the subsequent reference and saves everything in nextBlockMap . A comment created immediately after the cloned element should ensure that we always have endNode .
+10
source share

All Articles