diff --git a/packages/core/src/hydration/utils.ts b/packages/core/src/hydration/utils.ts index c0ec8f33d04..44248924e8b 100644 --- a/packages/core/src/hydration/utils.ts +++ b/packages/core/src/hydration/utils.ts @@ -13,7 +13,7 @@ import {LContainer} from '../render3/interfaces/container'; import {getDocument} from '../render3/interfaces/document'; import {RElement, RNode} from '../render3/interfaces/renderer_dom'; import {isRootView} from '../render3/interfaces/type_checks'; -import {HEADER_OFFSET, LView, TVIEW, TViewType} from '../render3/interfaces/view'; +import {HEADER_OFFSET, HYDRATION, LView, TVIEW, TViewType} from '../render3/interfaces/view'; import {makeStateKey, StateKey, TransferState} from '../transfer_state'; import {assertDefined, assertEqual} from '../util/assert'; import type {HydrationContext} from './annotate'; @@ -39,6 +39,9 @@ import {DeferBlockTrigger, HydrateTriggerDetails} from '../defer/interfaces'; import {hoverEventNames, interactionEventNames} from '../defer/dom_triggers'; import {DEHYDRATED_BLOCK_REGISTRY} from '../defer/registry'; import {sharedMapFunction} from '../event_delegation_utils'; +import {isDetachedByI18n} from '../i18n/utils'; +import {isInSkipHydrationBlock} from '../render3/state'; +import {TNode} from '../render3/interfaces/node'; /** * The name of the key used in the TransferState collection, @@ -496,6 +499,22 @@ export function isDisconnectedNode(hydrationInfo: DehydratedView, index: number) return !!initDisconnectedNodes(hydrationInfo)?.has(index); } +/** + * Checks whether a node can be hydrated. + * @param lView View in which the node instance is placed. + * @param tNode Node to be checked. + */ +export function canHydrateNode(lView: LView, tNode: TNode): boolean { + const hydrationInfo = lView[HYDRATION]; + + return ( + hydrationInfo !== null && + !isInSkipHydrationBlock() && + !isDetachedByI18n(tNode) && + !isDisconnectedNode(hydrationInfo, tNode.index - HEADER_OFFSET) + ); +} + /** * Helper function to prepare text nodes for serialization by ensuring * that seperate logical text blocks in the DOM remain separate after diff --git a/packages/core/src/render3/instructions/element.ts b/packages/core/src/render3/instructions/element.ts index e0cbb5672da..96077afaa5a 100644 --- a/packages/core/src/render3/instructions/element.ts +++ b/packages/core/src/render3/instructions/element.ts @@ -17,13 +17,12 @@ import { hasSkipHydrationAttrOnTNode, } from '../../hydration/skip_hydration'; import { + canHydrateNode, getSerializedContainerViews, - isDisconnectedNode, markRNodeAsClaimedByHydration, markRNodeAsSkippedByHydration, setSegmentHead, } from '../../hydration/utils'; -import {isDetachedByI18n} from '../../i18n/utils'; import {assertDefined, assertEqual} from '../../util/assert'; import {clearElementContents, createElementNode} from '../dom_node_manipulation'; import {hasClassInput, hasStyleInput, TNode, TNodeType} from '../interfaces/node'; @@ -40,7 +39,6 @@ import { getLView, getNamespace, getTView, - isInSkipHydrationBlock, isSkipHydrationRootTNode, lastNodeWasCreated, leaveSkipHydrationBlock, @@ -192,12 +190,7 @@ function locateOrCreateElementNodeImpl( name: string, index: number, ): RElement { - const hydrationInfo = lView[HYDRATION]; - const isNodeCreationMode = - !hydrationInfo || - isInSkipHydrationBlock() || - isDetachedByI18n(tNode) || - isDisconnectedNode(hydrationInfo, index); + const isNodeCreationMode = !canHydrateNode(lView, tNode); lastNodeWasCreated(isNodeCreationMode); // Regular creation mode. @@ -206,6 +199,7 @@ function locateOrCreateElementNodeImpl( } // Hydration mode, looking up an existing element in DOM. + const hydrationInfo = lView[HYDRATION]!; const native = locateNextRNode(hydrationInfo, tView, lView, tNode)!; ngDevMode && validateMatchingNode(native, Node.ELEMENT_NODE, name, lView, tNode); ngDevMode && markRNodeAsClaimedByHydration(native); diff --git a/packages/core/src/render3/instructions/element_container.ts b/packages/core/src/render3/instructions/element_container.ts index 43563b9aee3..59567cea011 100644 --- a/packages/core/src/render3/instructions/element_container.ts +++ b/packages/core/src/render3/instructions/element_container.ts @@ -8,12 +8,11 @@ import {validateMatchingNode, validateNodeExists} from '../../hydration/error_handling'; import {locateNextRNode, siblingAfter} from '../../hydration/node_lookup_utils'; import { + canHydrateNode, getNgContainerSize, - isDisconnectedNode, markRNodeAsClaimedByHydration, setSegmentHead, } from '../../hydration/utils'; -import {isDetachedByI18n} from '../../i18n/utils'; import {assertDefined, assertEqual, assertNumber} from '../../util/assert'; import {createCommentNode} from '../dom_node_manipulation'; import {TNode, TNodeType} from '../interfaces/node'; @@ -26,7 +25,6 @@ import { getCurrentTNode, getLView, getTView, - isInSkipHydrationBlock, lastNodeWasCreated, } from '../state'; import {elementLikeEndShared, elementLikeStartShared} from './shared'; @@ -134,12 +132,7 @@ function locateOrCreateElementContainerNode( index: number, ): RComment { let comment: RComment; - const hydrationInfo = lView[HYDRATION]; - const isNodeCreationMode = - !hydrationInfo || - isInSkipHydrationBlock() || - isDisconnectedNode(hydrationInfo, index) || - isDetachedByI18n(tNode); + const isNodeCreationMode = !canHydrateNode(lView, tNode); lastNodeWasCreated(isNodeCreationMode); @@ -149,6 +142,7 @@ function locateOrCreateElementContainerNode( } // Hydration mode, looking up existing elements in DOM. + const hydrationInfo = lView[HYDRATION]!; const currentRNode = locateNextRNode(hydrationInfo, tView, lView, tNode)!; ngDevMode && validateNodeExists(currentRNode, lView, tNode); diff --git a/packages/core/src/render3/instructions/template.ts b/packages/core/src/render3/instructions/template.ts index 0789f9b3501..86e7b39fcb5 100644 --- a/packages/core/src/render3/instructions/template.ts +++ b/packages/core/src/render3/instructions/template.ts @@ -10,11 +10,10 @@ import {TEMPLATES} from '../../hydration/interfaces'; import {locateNextRNode, siblingAfter} from '../../hydration/node_lookup_utils'; import { calcSerializedContainerSize, - isDisconnectedNode, + canHydrateNode, markRNodeAsClaimedByHydration, setSegmentHead, } from '../../hydration/utils'; -import {isDetachedByI18n} from '../../i18n/utils'; import {populateDehydratedViewsInLContainer} from '../../linker/view_container_ref'; import {assertEqual} from '../../util/assert'; import {assertFirstCreatePass} from '../assert'; @@ -263,12 +262,7 @@ function locateOrCreateContainerAnchorImpl( tNode: TNode, index: number, ): RComment { - const hydrationInfo = lView[HYDRATION]; - const isNodeCreationMode = - !hydrationInfo || - isInSkipHydrationBlock() || - isDetachedByI18n(tNode) || - isDisconnectedNode(hydrationInfo, index); + const isNodeCreationMode = !canHydrateNode(lView, tNode); lastNodeWasCreated(isNodeCreationMode); // Regular creation mode. @@ -276,6 +270,7 @@ function locateOrCreateContainerAnchorImpl( return createContainerAnchorImpl(tView, lView, tNode, index); } + const hydrationInfo = lView[HYDRATION]!; const ssrId = hydrationInfo.data[TEMPLATES]?.[index] ?? null; // Apply `ssrId` value to the underlying TView if it was not previously set. diff --git a/packages/core/src/render3/instructions/text.ts b/packages/core/src/render3/instructions/text.ts index 722d7446818..f379d25680d 100644 --- a/packages/core/src/render3/instructions/text.ts +++ b/packages/core/src/render3/instructions/text.ts @@ -7,8 +7,7 @@ */ import {validateMatchingNode} from '../../hydration/error_handling'; import {locateNextRNode} from '../../hydration/node_lookup_utils'; -import {isDisconnectedNode, markRNodeAsClaimedByHydration} from '../../hydration/utils'; -import {isDetachedByI18n} from '../../i18n/utils'; +import {canHydrateNode, markRNodeAsClaimedByHydration} from '../../hydration/utils'; import {assertEqual, assertIndexInRange} from '../../util/assert'; import {TElementNode, TNode, TNodeType} from '../interfaces/node'; import {RText} from '../interfaces/renderer_dom'; @@ -19,7 +18,6 @@ import { getBindingIndex, getLView, getTView, - isInSkipHydrationBlock, lastNodeWasCreated, setCurrentTNode, wasLastNodeCreated, @@ -84,12 +82,7 @@ function locateOrCreateTextNodeImpl( value: string, index: number, ): RText { - const hydrationInfo = lView[HYDRATION]; - const isNodeCreationMode = - !hydrationInfo || - isInSkipHydrationBlock() || - isDetachedByI18n(tNode) || - isDisconnectedNode(hydrationInfo, index); + const isNodeCreationMode = !canHydrateNode(lView, tNode); lastNodeWasCreated(isNodeCreationMode); // Regular creation mode. @@ -98,6 +91,7 @@ function locateOrCreateTextNodeImpl( } // Hydration mode, looking up an existing element in DOM. + const hydrationInfo = lView[HYDRATION]!; const textNative = locateNextRNode(hydrationInfo, tView, lView, tNode) as RText; ngDevMode && validateMatchingNode(textNative, Node.TEXT_NODE, null, lView, tNode); diff --git a/packages/core/test/bundling/hydration/bundle.golden_symbols.json b/packages/core/test/bundling/hydration/bundle.golden_symbols.json index bfa08f60ab0..68f8e0711d5 100644 --- a/packages/core/test/bundling/hydration/bundle.golden_symbols.json +++ b/packages/core/test/bundling/hydration/bundle.golden_symbols.json @@ -191,6 +191,7 @@ "callHook", "callHookInternal", "callHooks", + "canHydrateNode", "checkStable", "cleanUpView", "cleanupDehydratedIcuData",