@@ -1182,10 +1182,9 @@ angularWidget('a', function() {
11821182 * @name angular.widget.@ng :repeat
11831183 *
11841184 * @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.
11891188 *
11901189 * Special properties are exposed on the local scope of each template instance, including:
11911190 *
@@ -1256,68 +1255,89 @@ angularWidget('@ng:repeat', function(expression, element){
12561255 valueIdent = match [ 3 ] || match [ 1 ] ;
12571256 keyIdent = match [ 2 ] ;
12581257
1259- var childScopes = [ ] ;
1260- var childElements = [ iterStartElement ] ;
12611258 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 ( ) ;
12621267 this . $watch ( function ( scope ) {
12631268 var index = 0 ,
1264- childCount = childScopes . length ,
12651269 collection = scope . $eval ( rhs ) ,
12661270 collectionLength = size ( collection , true ) ,
1267- fragment = document . createDocumentFragment ( ) ,
1268- addFragmentTo = ( childCount < collectionLength ) ? childElements [ childCount ] : null ,
12691271 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
12711278
12721279 for ( key in collection ) {
12731280 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+ }
12831300 } else {
1284- // grow children
1301+ // new item which we don't know about
12851302 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 ) {
12931313 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 ) ;
13021321 } ) ;
13031322 }
1323+
13041324 index ++ ;
13051325 }
13061326 }
13071327
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+ }
13131338 }
13141339
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 ;
13211341 } ) ;
13221342 } ;
13231343} ) ;
0 commit comments