@@ -1182,10 +1182,9 @@ angularWidget('a', function() {
1182
1182
* @name angular.widget.@ng :repeat
1183
1183
*
1184
1184
* @description
1185
- * The `ng:repeat` widget instantiates a template once per item from a collection. The collection is
1186
- * enumerated with the `ng:repeat-index` attribute, starting from 0. Each template instance gets
1187
- * its own scope, where the given loop variable is set to the current collection item, and `$index`
1188
- * is set to the item index or key.
1185
+ * The `ng:repeat` widget instantiates a template once per item from a collection. Each template
1186
+ * instance gets its own scope, where the given loop variable is set to the current collection item,
1187
+ * and `$index` is set to the item index or key.
1189
1188
*
1190
1189
* Special properties are exposed on the local scope of each template instance, including:
1191
1190
*
@@ -1256,68 +1255,89 @@ angularWidget('@ng:repeat', function(expression, element){
1256
1255
valueIdent = match [ 3 ] || match [ 1 ] ;
1257
1256
keyIdent = match [ 2 ] ;
1258
1257
1259
- var childScopes = [ ] ;
1260
- var childElements = [ iterStartElement ] ;
1261
1258
var parentScope = this ;
1259
+ // Store a list of elements from previous run. This is a hash where key is the item from the
1260
+ // iterator, and the value is an array of objects with following properties.
1261
+ // - scope: bound scope
1262
+ // - element: previous element.
1263
+ // - index: position
1264
+ // We need an array of these objects since the same object can be returned from the iterator.
1265
+ // We expect this to be a rare case.
1266
+ var lastOrder = new HashQueueMap ( ) ;
1262
1267
this . $watch ( function ( scope ) {
1263
1268
var index = 0 ,
1264
- childCount = childScopes . length ,
1265
1269
collection = scope . $eval ( rhs ) ,
1266
1270
collectionLength = size ( collection , true ) ,
1267
- fragment = document . createDocumentFragment ( ) ,
1268
- addFragmentTo = ( childCount < collectionLength ) ? childElements [ childCount ] : null ,
1269
1271
childScope ,
1270
- key ;
1272
+ // Same as lastOrder but it has the current state. It will become the
1273
+ // lastOrder on the next iteration.
1274
+ nextOrder = new HashQueueMap ( ) ,
1275
+ key , value , // key/value of iteration
1276
+ array , last , // last object information {scope, element, index}
1277
+ cursor = iterStartElement ; // current position of the node
1271
1278
1272
1279
for ( key in collection ) {
1273
1280
if ( collection . hasOwnProperty ( key ) ) {
1274
- if ( index < childCount ) {
1275
- // reuse existing child
1276
- childScope = childScopes [ index ] ;
1277
- childScope [ valueIdent ] = collection [ key ] ;
1278
- if ( keyIdent ) childScope [ keyIdent ] = key ;
1279
- childScope . $position = index == 0
1280
- ? 'first'
1281
- : ( index == collectionLength - 1 ? 'last' : 'middle' ) ;
1282
- childScope . $eval ( ) ;
1281
+ last = lastOrder . shift ( value = collection [ key ] ) ;
1282
+ if ( last ) {
1283
+ // if we have already seen this object, then we need to reuse the
1284
+ // associated scope/element
1285
+ childScope = last . scope ;
1286
+ nextOrder . push ( value , last ) ;
1287
+
1288
+ if ( index === last . index ) {
1289
+ // do nothing
1290
+ cursor = last . element ;
1291
+ } else {
1292
+ // existing item which got moved
1293
+ last . index = index ;
1294
+ // This may be a noop, if the element is next, but I don't know of a good way to
1295
+ // figure this out, since it would require extra DOM access, so let's just hope that
1296
+ // the browsers realizes that it is noop, and treats it as such.
1297
+ cursor . after ( last . element ) ;
1298
+ cursor = last . element ;
1299
+ }
1283
1300
} else {
1284
- // grow children
1301
+ // new item which we don't know about
1285
1302
childScope = parentScope . $new ( ) ;
1286
- childScope [ valueIdent ] = collection [ key ] ;
1287
- if ( keyIdent ) childScope [ keyIdent ] = key ;
1288
- childScope . $index = index ;
1289
- childScope . $position = index == 0
1290
- ? 'first'
1291
- : ( index == collectionLength - 1 ? 'last' : 'middle' ) ;
1292
- childScopes . push ( childScope ) ;
1303
+ }
1304
+
1305
+ childScope [ valueIdent ] = collection [ key ] ;
1306
+ if ( keyIdent ) childScope [ keyIdent ] = key ;
1307
+ childScope . $index = index ;
1308
+ childScope . $position = index == 0
1309
+ ? 'first'
1310
+ : ( index == collectionLength - 1 ? 'last' : 'middle' ) ;
1311
+
1312
+ if ( ! last ) {
1293
1313
linker ( childScope , function ( clone ) {
1294
- clone . attr ( 'ng:repeat-index' , index ) ;
1295
- fragment . appendChild ( clone [ 0 ] ) ;
1296
- // TODO(misko): Temporary hack - maybe think about it - removed after we add fragment after $digest()
1297
- // This causes double $digest for children
1298
- // The first flush will couse a lot of DOM access (initial)
1299
- // Second flush shuld be noop since nothing has change hence no DOM access.
1300
- childScope . $digest ( ) ;
1301
- childElements [ index + 1 ] = clone ;
1314
+ cursor . after ( clone ) ;
1315
+ last = {
1316
+ scope : childScope ,
1317
+ element : ( cursor = clone ) ,
1318
+ index : index
1319
+ } ;
1320
+ nextOrder . push ( value , last ) ;
1302
1321
} ) ;
1303
1322
}
1323
+
1304
1324
index ++ ;
1305
1325
}
1306
1326
}
1307
1327
1308
- //attach new nodes buffered in doc fragment
1309
- if ( addFragmentTo ) {
1310
- // TODO(misko): For performance reasons, we should do the addition after all other widgets
1311
- // have run. For this should happend after $digest() is done!
1312
- addFragmentTo . after ( jqLite ( fragment ) ) ;
1328
+ //shrink children
1329
+ for ( key in lastOrder ) {
1330
+ if ( lastOrder . hasOwnProperty ( key ) ) {
1331
+ array = lastOrder [ key ] ;
1332
+ while ( array . length ) {
1333
+ value = array . pop ( ) ;
1334
+ value . element . remove ( ) ;
1335
+ value . scope . $destroy ( ) ;
1336
+ }
1337
+ }
1313
1338
}
1314
1339
1315
- // shrink children
1316
- while ( childScopes . length > index ) {
1317
- // can not use $destroy(true) since there may be multiple iterators on same parent.
1318
- childScopes . pop ( ) . $destroy ( ) ;
1319
- childElements . pop ( ) . remove ( ) ;
1320
- }
1340
+ lastOrder = nextOrder ;
1321
1341
} ) ;
1322
1342
} ;
1323
1343
} ) ;
0 commit comments