77 */
88
99import { DEHYDRATED_VIEWS , LContainer } from '../render3/interfaces/container' ;
10+ import { TNode , TNodeFlags } from '../render3/interfaces/node' ;
1011import { RNode } from '../render3/interfaces/renderer_dom' ;
12+ import { isLContainer } from '../render3/interfaces/type_checks' ;
13+ import { LView , TVIEW } from '../render3/interfaces/view' ;
1114
1215import { removeDehydratedViews } from './cleanup' ;
1316import {
@@ -61,6 +64,20 @@ export function locateDehydratedViewsInContainer(
6164 */
6265let _findMatchingDehydratedViewImpl : typeof findMatchingDehydratedViewImpl = ( ) => null ;
6366
67+ /**
68+ * Reference to a function that searches for a matching dehydrated view
69+ * stored on a control flow lContainer and removes the dehydrated content
70+ * once found.
71+ * Returns `null` by default, when hydration is not enabled.
72+ */
73+ let _findAndReconcileMatchingDehydratedViewsImpl : typeof findAndReconcileMatchingDehydratedViewsImpl =
74+ ( ) => null ;
75+
76+ export function enableFindMatchingDehydratedViewImpl ( ) {
77+ _findMatchingDehydratedViewImpl = findMatchingDehydratedViewImpl ;
78+ _findAndReconcileMatchingDehydratedViewsImpl = findAndReconcileMatchingDehydratedViewsImpl ;
79+ }
80+
6481/**
6582 * Retrieves the next dehydrated view from the LContainer and verifies that
6683 * it matches a given template id (from the TView that was used to create this
@@ -74,17 +91,8 @@ function findMatchingDehydratedViewImpl(
7491 lContainer : LContainer ,
7592 template : string | null ,
7693) : DehydratedContainerView | null {
77- const views = lContainer [ DEHYDRATED_VIEWS ] ;
78- if ( ! template || views === null || views . length === 0 ) {
79- return null ;
80- }
81- const view = views [ 0 ] ;
82- // Verify whether the first dehydrated view in the container matches
83- // the template id passed to this function (that originated from a TView
84- // that was used to create an instance of an embedded or component views.
85- if ( view . data [ TEMPLATE_ID ] === template ) {
86- // If the template id matches - extract the first view and return it.
87- return views . shift ( ) ! ;
94+ if ( hasMatchingDehydratedView ( lContainer , template ) ) {
95+ return lContainer [ DEHYDRATED_VIEWS ] ! . shift ( ) ! ;
8896 } else {
8997 // Otherwise, we are at the state when reconciliation can not be completed,
9098 // thus we remove all dehydrated views within this container (remove them
@@ -95,13 +103,101 @@ function findMatchingDehydratedViewImpl(
95103 }
96104}
97105
98- export function enableFindMatchingDehydratedViewImpl ( ) {
99- _findMatchingDehydratedViewImpl = findMatchingDehydratedViewImpl ;
100- }
101-
102106export function findMatchingDehydratedView (
103107 lContainer : LContainer ,
104108 template : string | null ,
105109) : DehydratedContainerView | null {
106110 return _findMatchingDehydratedViewImpl ( lContainer , template ) ;
107111}
112+
113+ export function findAndReconcileMatchingDehydratedViewsImpl (
114+ lContainer : LContainer ,
115+ templateTNode : TNode ,
116+ hostLView : LView ,
117+ ) : DehydratedContainerView | null {
118+ if ( templateTNode . tView ! . ssrId === null ) return null ;
119+ const dehydratedView = findMatchingDehydratedView ( lContainer , templateTNode . tView ! . ssrId ) ;
120+
121+ // we know that an ssrId was generated, but we were unable to match it to
122+ // a dehydrated view, which means that we may have changed branches
123+ // between server and client. We'll need to find and remove those
124+ // stale dehydrated views.
125+ if ( hostLView [ TVIEW ] . firstUpdatePass && dehydratedView === null ) {
126+ removeStaleDehydratedBranch ( hostLView , templateTNode ) ;
127+ }
128+ return dehydratedView ;
129+ }
130+
131+ export function findAndReconcileMatchingDehydratedViews (
132+ lContainer : LContainer ,
133+ templateTNode : TNode ,
134+ hostLView : LView ,
135+ ) : DehydratedContainerView | null {
136+ return _findAndReconcileMatchingDehydratedViewsImpl ( lContainer , templateTNode , hostLView ) ;
137+ }
138+
139+ /**
140+ * In the case that we have control flow that changes branches between server and
141+ * client, we're left with dehydrated content that will not be used. We need to find
142+ * it and clean it up at the right time so that we don't see duplicate content for
143+ * a few moments before the application reaches stability. This navigates the
144+ * control flow containers by looking at the TNodeFlags to find the matching
145+ * dehydrated content for the branch that is now stale from the server and removes it.
146+ */
147+ function removeStaleDehydratedBranch ( hostLView : LView , tNode : TNode ) : void {
148+ let currentTNode : TNode | null = tNode ;
149+ while ( currentTNode ) {
150+ // We can return here if we've found the dehydrated view and cleaned it up.
151+ // Otherwise we continue on until we either find it or reach the start of
152+ // the control flow.
153+ if ( cleanupMatchingDehydratedViews ( hostLView , currentTNode ) ) return ;
154+
155+ if ( ( currentTNode . flags & TNodeFlags . isControlFlowStart ) === TNodeFlags . isControlFlowStart ) {
156+ // we've hit the top of the control flow loop
157+ break ;
158+ }
159+
160+ currentTNode = currentTNode . prev ;
161+ }
162+
163+ currentTNode = tNode . next ; // jump to place we started so we can navigate down from there
164+
165+ while ( currentTNode ) {
166+ if ( ( currentTNode . flags & TNodeFlags . isInControlFlow ) !== TNodeFlags . isInControlFlow ) {
167+ // we've exited control flow and need to exit the loop.
168+ break ;
169+ }
170+
171+ // Similar to above, we can return here if we've found the dehydrated view
172+ // and cleaned it up. Otherwise we continue on until we either find it or
173+ // reach the end of the control flow.
174+ if ( cleanupMatchingDehydratedViews ( hostLView , currentTNode ) ) return ;
175+
176+ currentTNode = currentTNode . next ;
177+ }
178+ }
179+
180+ function hasMatchingDehydratedView ( lContainer : LContainer , template : string | null ) : boolean {
181+ const views = lContainer [ DEHYDRATED_VIEWS ] ;
182+ if ( ! template || views === null || views . length === 0 ) {
183+ return false ;
184+ }
185+ // Verify whether the first dehydrated view in the container matches
186+ // the template id passed to this function (that originated from a TView
187+ // that was used to create an instance of an embedded or component views.
188+ return views [ 0 ] . data [ TEMPLATE_ID ] === template ;
189+ }
190+
191+ function cleanupMatchingDehydratedViews ( hostLView : LView , currentTNode : TNode ) : boolean {
192+ const ssrId = currentTNode . tView ?. ssrId ;
193+ if ( ssrId == null /* check both `null` and `undefined` */ ) return false ;
194+
195+ const container = hostLView [ currentTNode . index ] ;
196+ // if we can find the dehydrated view in this container, we know we've found the stale view
197+ // and we can remove it.
198+ if ( isLContainer ( container ) && hasMatchingDehydratedView ( container , ssrId ) ) {
199+ removeDehydratedViews ( container ) ;
200+ return true ;
201+ }
202+ return false ;
203+ }
0 commit comments