88
99import { ApplicationRef } from '../application_ref' ;
1010import { ViewEncapsulation } from '../metadata' ;
11+ import { Renderer2 } from '../render' ;
1112import { collectNativeNodes } from '../render3/collect_native_nodes' ;
1213import { getComponentDef } from '../render3/definition' ;
1314import { CONTAINER_HEADER_OFFSET , LContainer } from '../render3/interfaces/container' ;
1415import { TNode , TNodeType } from '../render3/interfaces/node' ;
1516import { RElement } from '../render3/interfaces/renderer_dom' ;
1617import { hasI18n , isComponentHost , isLContainer , isProjectionTNode , isRootView } from '../render3/interfaces/type_checks' ;
17- import { CONTEXT , FLAGS , HEADER_OFFSET , HOST , LView , LViewFlags , RENDERER , TView , TVIEW , TViewType } from '../render3/interfaces/view' ;
18+ import { CONTEXT , HEADER_OFFSET , HOST , LView , PARENT , RENDERER , TView , TVIEW , TViewType } from '../render3/interfaces/view' ;
1819import { unwrapRNode } from '../render3/util/view_utils' ;
1920import { TransferState } from '../transfer_state' ;
2021
2122import { unsupportedProjectionOfDomNodes } from './error_handling' ;
2223import { CONTAINERS , DISCONNECTED_NODES , ELEMENT_CONTAINERS , MULTIPLIER , NODES , NUM_ROOT_NODES , SerializedContainerView , SerializedView , TEMPLATE_ID , TEMPLATES } from './interfaces' ;
2324import { calcPathForNode } from './node_lookup_utils' ;
24- import { hasInSkipHydrationBlockFlag , isInSkipHydrationBlock , SKIP_HYDRATION_ATTR_NAME } from './skip_hydration' ;
25- import { getComponentLViewForHydration , NGH_ATTR_NAME , NGH_DATA_KEY , TextNodeMarker } from './utils' ;
25+ import { isInSkipHydrationBlock , SKIP_HYDRATION_ATTR_NAME } from './skip_hydration' ;
26+ import { getLNodeForHydration , NGH_ATTR_NAME , NGH_DATA_KEY , TextNodeMarker } from './utils' ;
2627
2728/**
2829 * A collection that tracks all serialized views (`ngh` DOM annotations)
@@ -91,6 +92,54 @@ function calcNumRootNodes(tView: TView, lView: LView, tNode: TNode|null): number
9192 return rootNodes . length ;
9293}
9394
95+ /**
96+ * Annotates root level component's LView for hydration,
97+ * see `annotateHostElementForHydration` for additional information.
98+ */
99+ function annotateComponentLViewForHydration ( lView : LView , context : HydrationContext ) : number | null {
100+ const hostElement = lView [ HOST ] ;
101+ // Root elements might also be annotated with the `ngSkipHydration` attribute,
102+ // check if it's present before starting the serialization process.
103+ if ( hostElement && ! ( hostElement as HTMLElement ) . hasAttribute ( SKIP_HYDRATION_ATTR_NAME ) ) {
104+ return annotateHostElementForHydration ( hostElement as HTMLElement , lView , context ) ;
105+ }
106+ return null ;
107+ }
108+
109+ /**
110+ * Annotates root level LContainer for hydration. This happens when a root component
111+ * injects ViewContainerRef, thus making the component an anchor for a view container.
112+ * This function serializes the component itself as well as all views from the view
113+ * container.
114+ */
115+ function annotateLContainerForHydration ( lContainer : LContainer , context : HydrationContext ) {
116+ const componentLView = lContainer [ HOST ] as LView < unknown > ;
117+
118+ // Serialize the root component itself.
119+ const componentLViewNghIndex = annotateComponentLViewForHydration ( componentLView , context ) ;
120+
121+ const hostElement = unwrapRNode ( componentLView [ HOST ] ! ) as HTMLElement ;
122+
123+ // Serialize all views within this view container.
124+ const rootLView = lContainer [ PARENT ] ;
125+ const rootLViewNghIndex = annotateHostElementForHydration ( hostElement , rootLView , context ) ;
126+
127+ const renderer = componentLView [ RENDERER ] as Renderer2 ;
128+
129+ // For cases when a root component also acts as an anchor node for a ViewContainerRef
130+ // (for example, when ViewContainerRef is injected in a root component), there is a need
131+ // to serialize information about the component itself, as well as an LContainer that
132+ // represents this ViewContainerRef. Effectively, we need to serialize 2 pieces of info:
133+ // (1) hydration info for the root component itself and (2) hydration info for the
134+ // ViewContainerRef instance (an LContainer). Each piece of information is included into
135+ // the hydration data (in the TransferState object) separately, thus we end up with 2 ids.
136+ // Since we only have 1 root element, we encode both bits of info into a single string:
137+ // ids are separated by the `|` char (e.g. `10|25`, where `10` is the ngh for a component view
138+ // and 25 is the `ngh` for a root view which holds LContainer).
139+ const finalIndex = `${ componentLViewNghIndex } |${ rootLViewNghIndex } ` ;
140+ renderer . setAttribute ( hostElement , NGH_ATTR_NAME , finalIndex ) ;
141+ }
142+
94143/**
95144 * Annotates all components bootstrapped in a given ApplicationRef
96145 * with info needed for hydration.
@@ -103,21 +152,21 @@ export function annotateForHydration(appRef: ApplicationRef, doc: Document) {
103152 const corruptedTextNodes = new Map < HTMLElement , TextNodeMarker > ( ) ;
104153 const viewRefs = appRef . _views ;
105154 for ( const viewRef of viewRefs ) {
106- const lView = getComponentLViewForHydration ( viewRef ) ;
155+ const lNode = getLNodeForHydration ( viewRef ) ;
156+
107157 // An `lView` might be `null` if a `ViewRef` represents
108158 // an embedded view (not a component view).
109- if ( lView !== null ) {
110- const hostElement = lView [ HOST ] ;
111- // Root elements might also be annotated with the `ngSkipHydration` attribute,
112- // check if it's present before starting the serialization process.
113- if ( hostElement && ! ( hostElement as HTMLElement ) . hasAttribute ( SKIP_HYDRATION_ATTR_NAME ) ) {
114- const context : HydrationContext = {
115- serializedViewCollection,
116- corruptedTextNodes,
117- } ;
118- annotateHostElementForHydration ( hostElement as HTMLElement , lView , context ) ;
119- insertCorruptedTextNodeMarkers ( corruptedTextNodes , doc ) ;
159+ if ( lNode !== null ) {
160+ const context : HydrationContext = {
161+ serializedViewCollection,
162+ corruptedTextNodes,
163+ } ;
164+ if ( isLContainer ( lNode ) ) {
165+ annotateLContainerForHydration ( lNode , context ) ;
166+ } else {
167+ annotateComponentLViewForHydration ( lNode , context ) ;
120168 }
169+ insertCorruptedTextNodeMarkers ( corruptedTextNodes , doc ) ;
121170 }
122171 }
123172
@@ -408,9 +457,11 @@ function componentUsesShadowDomEncapsulation(lView: LView): boolean {
408457 * @param element The Host element to be annotated
409458 * @param lView The associated LView
410459 * @param context The hydration context
460+ * @returns An index of serialized view from the transfer state object
461+ * or `null` when a given component can not be serialized.
411462 */
412463function annotateHostElementForHydration (
413- element : RElement , lView : LView , context : HydrationContext ) : void {
464+ element : RElement , lView : LView , context : HydrationContext ) : number | null {
414465 const renderer = lView [ RENDERER ] ;
415466 if ( hasI18n ( lView ) || componentUsesShadowDomEncapsulation ( lView ) ) {
416467 // Attach the skip hydration attribute if this component:
@@ -419,10 +470,12 @@ function annotateHostElementForHydration(
419470 // shadow DOM, so we can not guarantee that client and server representations
420471 // would exactly match
421472 renderer . setAttribute ( element , SKIP_HYDRATION_ATTR_NAME , '' ) ;
473+ return null ;
422474 } else {
423475 const ngh = serializeLView ( lView , context ) ;
424476 const index = context . serializedViewCollection . add ( ngh ) ;
425477 renderer . setAttribute ( element , NGH_ATTR_NAME , index . toString ( ) ) ;
478+ return index ;
426479 }
427480}
428481
0 commit comments