NgRepeatDirective class
The ngRepeat directive instantiates a template once per item from a collection. Each template
instance gets its own scope, where the given loop variable is set to the current collection item,
and $index is set to the item index or key.
Special properties are exposed on the local scope of each template instance, including:
| Variable | Type | Details | |
|---|---|---|---|
| `$index` | [num] | iterator offset of the repeated element (0..length-1) | |
| `$first` | [bool] | true if the repeated element is first in the iterator. | |
| `$middle` | [bool] | true if the repeated element is between the first and last in the iterator. | |
| `$last` | [bool] | true if the repeated element is last in the iterator. | |
| `$even` | [bool] | true if the iterator position `$index` is even (otherwise false). | |
| `$odd` | [bool] | true if the iterator position `$index` is odd (otherwise false). |
repeat_expression ngRepeat The expression indicating how to enumerate a collection. These
formats are currently supported:
-
variable in expression– where variable is the user defined loop variable andexpressionis a scope expression giving the collection to enumerate.For example:
album in artist.albums. -
variable in expression track by tracking_expression– You can also provide an optional tracking function which can be used to associate the objects in the collection with the DOM elements. If no tracking function is specified the ng-repeat associates elements by identity in the collection. It is an error to have more than one tracking function to resolve to the same key. (This would mean that two distinct objects are mapped to the same DOM element, which is not possible.) Filters should be applied to the expression, before specifying a tracking expression.For example:
item in itemsis equivalent to `item in items track by $id(item)'. This implies that the DOM elements will be associated by item identity in the array.For example:
item in items track by $id(item). A built in$id()function can be used to assign a unique$$hashKeyproperty to each item in the array. This property is then used as a key to associated DOM elements with the corresponding item in the array by identity. Moving the same object in array would move the DOM element in the same way ian the DOM.For example:
item in items track by item.idis a typical pattern when the items come from the database. In this case the object identity does not matter. Two objects are considered equivalent as long as theiridproperty is same.For example:
item in items | filter:searchText track by item.idis a pattern that might be used to apply a filter to items in conjunction with a tracking expression.
Example:
<ul ng-repeat="item in ['foo', 'bar', 'baz']">
<li>{{$item}}</li>
</ul>
@NgDirective(
children: NgAnnotation.TRANSCLUDE_CHILDREN,
selector: '[ng-repeat]',
map: const {'.': '@expression'})
class NgRepeatDirective {
static RegExp _SYNTAX = new RegExp(r'^\s*(.+)\s+in\s+(.*?)\s*(\s+track\s+by\s+(.+)\s*)?$');
static RegExp _LHS_SYNTAX = new RegExp(r'^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$');
BlockHole _blockHole;
BoundBlockFactory _boundBlockFactory;
Scope _scope;
String _expression;
String _valueIdentifier;
String _keyIdentifier;
String _listExpr;
Map<Object, _Row> _rows = new Map<dynamic, _Row>();
Function _trackByIdFn = (key, value, index) => value;
Function _removeWatch = () => null;
NgRepeatDirective(BlockHole this._blockHole,
BoundBlockFactory this._boundBlockFactory,
Scope this._scope);
set expression(value) {
_expression = value;
_removeWatch();
Match match = _SYNTAX.firstMatch(_expression);
if (match == null) {
throw "[NgErr7] ngRepeat error! Expected expression in form of '_item_ in _collection_[ track by _id_]' but got '$_expression'.";
}
_listExpr = match.group(2);
var assignExpr = match.group(1);
match = _LHS_SYNTAX.firstMatch(assignExpr);
if (match == null) {
throw "[NgErr8] ngRepeat error! '_item_' in '_item_ in _collection_' should be an identifier or '(_key_, _value_)' expression, but got '$assignExpr'.";
}
_valueIdentifier = match.group(3);
if (_valueIdentifier == null) _valueIdentifier = match.group(1);
_keyIdentifier = match.group(2);
_removeWatch = _scope.$watchCollection(_listExpr, _onCollectionChange, value);
}
List<_Row> _computeNewRows(collection, trackById) {
List<_Row> newRowOrder = [];
// Same as lastBlockMap but it has the current state. It will become the
// lastBlockMap on the next iteration.
Map<dynamic, _Row> newRows = new Map<dynamic, _Row>();
var arrayLength = collection.length;
// locate existing items
var length = newRowOrder.length = collection.length;
for (var index = 0; index < length; index++) {
var value = collection[index];
trackById = _trackByIdFn(index, value, index);
if (_rows.containsKey(trackById)) {
var row = _rows[trackById];
_rows.remove(trackById);
newRows[trackById] = row;
newRowOrder[index] = row;
} else if (newRows.containsKey(trackById)) {
// restore lastBlockMap
newRowOrder.forEach((row) {
if (row != null && row.startNode != null) {
_rows[row.id] = row;
}
});
// This is a duplicate and we need to throw an error
throw "[NgErr50] ngRepeat error! Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: $_expression, Duplicate key: $trackById";
} else {
// new never before seen row
newRowOrder[index] = new _Row(trackById);
newRows[trackById] = null;
}
}
// remove existing items
_rows.forEach((key, row){
row.block.remove();
row.scope.$destroy();
});
_rows = newRows;
return newRowOrder;
}
_onCollectionChange(collection) {
var previousNode = _blockHole.elements[0], // current position of the node
nextNode,
childScope,
trackById,
cursor = _blockHole;
if (collection is! List) {
collection = [];
}
List<_Row> newRowOrder = _computeNewRows(collection, trackById);
for (var index = 0, length = collection.length; index < length; index++) {
var key = index;
var value = collection[index];
_Row row = newRowOrder[index];
if (row.startNode != null) {
// if we have already seen this object, then we need to reuse the
// associated scope/element
childScope = row.scope;
nextNode = previousNode;
do {
nextNode = nextNode.nextNode;
} while(nextNode != null);
if (row.startNode == nextNode) {
// do nothing
} else {
// existing item which got moved
row.block.moveAfter(cursor);
}
previousNode = row.endNode;
} else {
// new item which we don't know about
childScope = _scope.$new();
}
childScope[_valueIdentifier] = value;
childScope[r'$index'] = index;
childScope[r'$first'] = (index == 0);
childScope[r'$last'] = (index == (collection.length - 1));
childScope[r'$middle'] = !(childScope.$first || childScope.$last);
childScope[r'$odd'] = index & 1 == 1;
childScope[r'$even'] = index & 1 == 0;
if (row.startNode == null) {
_rows[row.id] = row;
var block = _boundBlockFactory(childScope);
row.block = block;
row.scope = childScope;
row.elements = block.elements;
row.startNode = row.elements[0];
row.endNode = row.elements[row.elements.length - 1];
block.insertAfter(cursor);
}
cursor = row.block;
}
}
}
Constructors
new NgRepeatDirective(BlockHole _blockHole, BoundBlockFactory _boundBlockFactory, Scope _scope) #
Properties
dynamic set expression(value) #
set expression(value) {
_expression = value;
_removeWatch();
Match match = _SYNTAX.firstMatch(_expression);
if (match == null) {
throw "[NgErr7] ngRepeat error! Expected expression in form of '_item_ in _collection_[ track by _id_]' but got '$_expression'.";
}
_listExpr = match.group(2);
var assignExpr = match.group(1);
match = _LHS_SYNTAX.firstMatch(assignExpr);
if (match == null) {
throw "[NgErr8] ngRepeat error! '_item_' in '_item_ in _collection_' should be an identifier or '(_key_, _value_)' expression, but got '$assignExpr'.";
}
_valueIdentifier = match.group(3);
if (_valueIdentifier == null) _valueIdentifier = match.group(1);
_keyIdentifier = match.group(2);
_removeWatch = _scope.$watchCollection(_listExpr, _onCollectionChange, value);
}