66 * found in the LICENSE file at https://angular.io/license
77 */
88
9- import { DefaultIterableDiffer , IterableChangeRecord , TrackByFunction } from '../../change_detection' ;
9+ import { TrackByFunction } from '../../change_detection' ;
10+ import { DehydratedContainerView } from '../../hydration/interfaces' ;
1011import { findMatchingDehydratedView } from '../../hydration/views' ;
1112import { assertDefined } from '../../util/assert' ;
1213import { assertLContainer , assertLView , assertTNode } from '../assert' ;
1314import { bindingUpdated } from '../bindings' ;
1415import { CONTAINER_HEADER_OFFSET , LContainer } from '../interfaces/container' ;
1516import { ComponentTemplate } from '../interfaces/definition' ;
1617import { TNode } from '../interfaces/node' ;
17- import { CONTEXT , DECLARATION_COMPONENT_VIEW , HEADER_OFFSET , LView , TVIEW , TView } from '../interfaces/view' ;
18- import { detachView } from '../node_manipulation' ;
18+ import { CONTEXT , DECLARATION_COMPONENT_VIEW , HEADER_OFFSET , HYDRATION , LView , TVIEW , TView } from '../interfaces/view' ;
19+ import { LiveCollection , reconcile } from '../list_reconciliation' ;
20+ import { destroyLView , detachView } from '../node_manipulation' ;
1921import { getLView , nextBindingIndex } from '../state' ;
2022import { getTNode } from '../util/view_utils' ;
2123import { addLViewToLContainer , createAndRenderEmbeddedLView , getLViewFromLContainer , removeLViewFromLContainer , shouldAddViewToDom } from '../view_manipulation' ;
@@ -99,7 +101,7 @@ export function ɵɵrepeaterTrackByIdentity<T>(_: number, value: T) {
99101}
100102
101103class RepeaterMetadata {
102- constructor ( public hasEmptyBlock : boolean , public differ : DefaultIterableDiffer < unknown > ) { }
104+ constructor ( public hasEmptyBlock : boolean , public trackByFn : TrackByFunction < unknown > ) { }
103105}
104106
105107/**
@@ -135,7 +137,7 @@ export function ɵɵrepeaterCreate(
135137 // new function. For pure functions it's not necessary.
136138 trackByFn . bind ( hostLView [ DECLARATION_COMPONENT_VIEW ] [ CONTEXT ] ) :
137139 trackByFn ;
138- const metadata = new RepeaterMetadata ( hasEmptyBlock , new DefaultIterableDiffer ( boundTrackBy ) ) ;
140+ const metadata = new RepeaterMetadata ( hasEmptyBlock , boundTrackBy ) ;
139141 hostLView [ HEADER_OFFSET + index ] = metadata ;
140142
141143 ɵɵtemplate ( index + 1 , templateFn , decls , vars ) ;
@@ -150,6 +152,48 @@ export function ɵɵrepeaterCreate(
150152 }
151153}
152154
155+ class LiveCollectionLContainerImpl extends
156+ LiveCollection < LView < RepeaterContext < unknown > > , RepeaterContext < unknown > > {
157+ constructor (
158+ private lContainer : LContainer , private hostLView : LView , private templateTNode : TNode ,
159+ private trackByFn : TrackByFunction < unknown > ) {
160+ super ( ) ;
161+ }
162+
163+ override get length ( ) : number {
164+ return this . lContainer . length - CONTAINER_HEADER_OFFSET ;
165+ }
166+ override at ( index : number ) : LView < RepeaterContext < unknown > > {
167+ return getExistingLViewFromLContainer ( this . lContainer , index ) ;
168+ }
169+ override key ( index : number ) : unknown {
170+ return this . trackByFn ( index , this . at ( index ) [ CONTEXT ] . $implicit ) ;
171+ }
172+ override attach ( index : number , lView : LView < RepeaterContext < unknown > > ) : void {
173+ const dehydratedView = lView [ HYDRATION ] as DehydratedContainerView ;
174+ addLViewToLContainer (
175+ this . lContainer , lView , index , shouldAddViewToDom ( this . templateTNode , dehydratedView ) ) ;
176+ }
177+ override detach ( index : number ) : LView < RepeaterContext < unknown > > {
178+ return detachExistingView < RepeaterContext < unknown > > ( this . lContainer , index ) ;
179+ }
180+ override create ( index : number , value : unknown ) : LView < RepeaterContext < unknown > > {
181+ const dehydratedView =
182+ findMatchingDehydratedView ( this . lContainer , this . templateTNode . tView ! . ssrId ) ;
183+ const embeddedLView = createAndRenderEmbeddedLView (
184+ this . hostLView , this . templateTNode , new RepeaterContext ( this . lContainer , value , index ) ,
185+ { dehydratedView} ) ;
186+
187+ return embeddedLView ;
188+ }
189+ override destroy ( lView : LView < RepeaterContext < unknown > > ) : void {
190+ destroyLView ( lView [ TVIEW ] , lView ) ;
191+ }
192+ override updateValue ( index : number , value : unknown ) : void {
193+ this . at ( index ) [ CONTEXT ] . $implicit = value ;
194+ }
195+ }
196+
153197/**
154198 * The repeater instruction does update-time diffing of a provided collection (against the
155199 * collection seen previously) and maps changes in the collection to views structure (by adding,
@@ -165,79 +209,43 @@ export function ɵɵrepeater(
165209 const hostLView = getLView ( ) ;
166210 const hostTView = hostLView [ TVIEW ] ;
167211 const metadata = hostLView [ HEADER_OFFSET + metadataSlotIdx ] as RepeaterMetadata ;
212+ const containerIndex = metadataSlotIdx + 1 ;
213+ const lContainer = getLContainer ( hostLView , HEADER_OFFSET + containerIndex ) ;
214+ const itemTemplateTNode = getExistingTNode ( hostTView , containerIndex ) ;
168215
169- const differ = metadata . differ ;
170- const changes = differ . diff ( collection ) ;
171-
172- // handle repeater changes
173- if ( changes !== null ) {
174- const containerIndex = metadataSlotIdx + 1 ;
175- const itemTemplateTNode = getExistingTNode ( hostTView , containerIndex ) ;
176- const lContainer = getLContainer ( hostLView , HEADER_OFFSET + containerIndex ) ;
177- let needsIndexUpdate = false ;
178- changes . forEachOperation (
179- ( item : IterableChangeRecord < unknown > , adjustedPreviousIndex : number | null ,
180- currentIndex : number | null ) => {
181- if ( item . previousIndex === null ) {
182- // add
183- const newViewIdx = adjustToLastLContainerIndex ( lContainer , currentIndex ) ;
184- const dehydratedView =
185- findMatchingDehydratedView ( lContainer , itemTemplateTNode . tView ! . ssrId ) ;
186- const embeddedLView = createAndRenderEmbeddedLView (
187- hostLView , itemTemplateTNode ,
188- new RepeaterContext ( lContainer , item . item , newViewIdx ) , { dehydratedView} ) ;
189- addLViewToLContainer (
190- lContainer , embeddedLView , newViewIdx ,
191- shouldAddViewToDom ( itemTemplateTNode , dehydratedView ) ) ;
192- needsIndexUpdate = true ;
193- } else if ( currentIndex === null ) {
194- // remove
195- adjustedPreviousIndex = adjustToLastLContainerIndex ( lContainer , adjustedPreviousIndex ) ;
196- removeLViewFromLContainer ( lContainer , adjustedPreviousIndex ) ;
197- needsIndexUpdate = true ;
198- } else if ( adjustedPreviousIndex !== null ) {
199- // move
200- const existingLView =
201- detachExistingView < RepeaterContext < unknown > > ( lContainer , adjustedPreviousIndex ) ;
202- addLViewToLContainer ( lContainer , existingLView , currentIndex ) ;
203- needsIndexUpdate = true ;
204- }
205- } ) ;
206-
207- // A trackBy function might return the same value even if the underlying item changed - re-bind
208- // it in the context.
209- changes . forEachIdentityChange ( ( record : IterableChangeRecord < unknown > ) => {
210- const viewIdx = adjustToLastLContainerIndex ( lContainer , record . currentIndex ) ;
211- const lView = getExistingLViewFromLContainer < RepeaterContext < unknown > > ( lContainer , viewIdx ) ;
212- lView [ CONTEXT ] . $implicit = record . item ;
213- } ) ;
216+ reconcile (
217+ new LiveCollectionLContainerImpl (
218+ lContainer , hostLView , itemTemplateTNode , metadata . trackByFn ) ,
219+ collection , metadata . trackByFn ) ;
214220
215- // moves in the container might caused context's index to get out of order, re-adjust
216- if ( needsIndexUpdate ) {
217- for ( let i = 0 ; i < lContainer . length - CONTAINER_HEADER_OFFSET ; i ++ ) {
218- const lView = getExistingLViewFromLContainer < RepeaterContext < unknown > > ( lContainer , i ) ;
219- lView [ CONTEXT ] . $index = i ;
220- }
221- }
221+ // moves in the container might caused context's index to get out of order, re-adjust
222+ // PERF: we could try to book-keep moves and do this index re-adjust as need, at the cost of the
223+ // additional code complexity
224+ for ( let i = 0 ; i < lContainer . length - CONTAINER_HEADER_OFFSET ; i ++ ) {
225+ const lView = getExistingLViewFromLContainer < RepeaterContext < unknown > > ( lContainer , i ) ;
226+ lView [ CONTEXT ] . $index = i ;
222227 }
223228
224229 // handle empty blocks
230+ // PERF: maybe I could skip allocation of memory for the empty block? Isn't it the "fix" on the
231+ // compiler side that we've been discussing? Talk to K & D!
225232 const bindingIndex = nextBindingIndex ( ) ;
226233 if ( metadata . hasEmptyBlock ) {
227- const hasItemsInCollection = differ . length > 0 ;
228- if ( bindingUpdated ( hostLView , bindingIndex , hasItemsInCollection ) ) {
234+ const isCollectionEmpty = lContainer . length - CONTAINER_HEADER_OFFSET === 0 ;
235+ if ( bindingUpdated ( hostLView , bindingIndex , isCollectionEmpty ) ) {
229236 const emptyTemplateIndex = metadataSlotIdx + 2 ;
230- const lContainer = getLContainer ( hostLView , HEADER_OFFSET + emptyTemplateIndex ) ;
231- if ( hasItemsInCollection ) {
232- removeLViewFromLContainer ( lContainer , 0 ) ;
233- } else {
237+ const lContainerForEmpty = getLContainer ( hostLView , HEADER_OFFSET + emptyTemplateIndex ) ;
238+ if ( isCollectionEmpty ) {
234239 const emptyTemplateTNode = getExistingTNode ( hostTView , emptyTemplateIndex ) ;
235240 const dehydratedView =
236- findMatchingDehydratedView ( lContainer , emptyTemplateTNode . tView ! . ssrId ) ;
241+ findMatchingDehydratedView ( lContainerForEmpty , emptyTemplateTNode . tView ! . ssrId ) ;
237242 const embeddedLView = createAndRenderEmbeddedLView (
238243 hostLView , emptyTemplateTNode , undefined , { dehydratedView} ) ;
239244 addLViewToLContainer (
240- lContainer , embeddedLView , 0 , shouldAddViewToDom ( emptyTemplateTNode , dehydratedView ) ) ;
245+ lContainerForEmpty , embeddedLView , 0 ,
246+ shouldAddViewToDom ( emptyTemplateTNode , dehydratedView ) ) ;
247+ } else {
248+ removeLViewFromLContainer ( lContainerForEmpty , 0 ) ;
241249 }
242250 }
243251 }
@@ -250,10 +258,6 @@ function getLContainer(lView: LView, index: number): LContainer {
250258 return lContainer ;
251259}
252260
253- function adjustToLastLContainerIndex ( lContainer : LContainer , index : number | null ) : number {
254- return index !== null ? index : lContainer . length - CONTAINER_HEADER_OFFSET ;
255- }
256-
257261function detachExistingView < T > ( lContainer : LContainer , index : number ) : LView < T > {
258262 const existingLView = detachView ( lContainer , index ) ;
259263 ngDevMode && assertLView ( existingLView ) ;
0 commit comments