diff --git a/packages/core/src/hydration/annotate.ts b/packages/core/src/hydration/annotate.ts index bcb042d1ad7..a55f15c9ead 100644 --- a/packages/core/src/hydration/annotate.ts +++ b/packages/core/src/hydration/annotate.ts @@ -405,7 +405,6 @@ function serializeLContainer( // Add defer block into info context.deferBlocks const deferBlockInfo: SerializedDeferBlock = { - [DEFER_PARENT_BLOCK_ID]: parentDeferBlockId, [NUM_ROOT_NODES]: rootNodes.length, [DEFER_BLOCK_STATE]: lDetails[CURRENT_DEFER_BLOCK_STATE], }; @@ -415,6 +414,11 @@ function serializeLContainer( deferBlockInfo[DEFER_HYDRATE_TRIGGERS] = serializedTriggers; } + if (parentDeferBlockId !== null) { + // Serialize parent id only when it's present. + deferBlockInfo[DEFER_PARENT_BLOCK_ID] = parentDeferBlockId; + } + context.deferBlocks.set(deferBlockId, deferBlockInfo); const node = unwrapRNode(lContainer); diff --git a/packages/core/src/hydration/interfaces.ts b/packages/core/src/hydration/interfaces.ts index 5808b179571..c66ee9dd3e5 100644 --- a/packages/core/src/hydration/interfaces.ts +++ b/packages/core/src/hydration/interfaces.ts @@ -158,7 +158,7 @@ export interface SerializedDeferBlock { /** * This contains the unique id of this defer block's parent, if it exists. */ - [DEFER_PARENT_BLOCK_ID]: string | null; + [DEFER_PARENT_BLOCK_ID]?: string; /** * This field represents a status, based on the `DeferBlockState` enum. diff --git a/packages/core/src/hydration/utils.ts b/packages/core/src/hydration/utils.ts index eb9b549d359..49ee3b126e4 100644 --- a/packages/core/src/hydration/utils.ts +++ b/packages/core/src/hydration/utils.ts @@ -571,7 +571,7 @@ export function getParentBlockHydrationQueue( const deferBlockParents = transferState.get(NGH_DEFER_BLOCKS_KEY, {}); let isTopMostDeferBlock = false; - let currentBlockId: string | null = deferBlockId; + let currentBlockId: string | undefined = deferBlockId; let parentBlockPromise: Promise | null = null; const hydrationQueue: string[] = []; diff --git a/packages/platform-server/test/incremental_hydration_spec.ts b/packages/platform-server/test/incremental_hydration_spec.ts index 44e0c8b4665..69263d96389 100644 --- a/packages/platform-server/test/incremental_hydration_spec.ts +++ b/packages/platform-server/test/incremental_hydration_spec.ts @@ -222,7 +222,7 @@ describe('platform-server partial hydration integration', () => { const ssrContents = getAppContents(html); expect(ssrContents).toContain( - '"__nghDeferData__":{"d0":{"p":null,"r":1,"s":2},"d1":{"p":"d0","r":2,"s":2}}', + '"__nghDeferData__":{"d0":{"r":1,"s":2},"d1":{"r":2,"s":2,"p":"d0"}}', ); }); @@ -285,9 +285,38 @@ describe('platform-server partial hydration integration', () => { const ssrContents = getAppContents(html); expect(ssrContents).toContain( - '"__nghDeferData__":{"d0":{"p":null,"r":1,"s":2},"d1":{"p":"d0","r":2,"s":2,"t":[2]}}', + '"__nghDeferData__":{"d0":{"r":1,"s":2},"d1":{"r":2,"s":2,"t":[2],"p":"d0"}}', ); }); + + it('should not include parent id in serialized data for top-level `@defer` blocks', async () => { + @Component({ + selector: 'app', + template: ` + @defer (on viewport; hydrate on interaction) { + Hello world! + } @placeholder { + Placeholder + } + `, + }) + class SimpleComponent {} + + const appId = 'custom-app-id'; + const providers = [{provide: APP_ID, useValue: appId}]; + const hydrationFeatures = () => [withIncrementalHydration()]; + + const html = await ssr(SimpleComponent, { + envProviders: providers, + hydrationFeatures, + }); + + const ssrContents = getAppContents(html); + + // Assert that the serialized data doesn't contain the "p" field, + // which contains parent id (which is not needed for top-level blocks). + expect(ssrContents).toContain('"__nghDeferData__":{"d0":{"r":1,"s":2}}}'); + }); }); describe('basic hydration behavior', () => { @@ -347,7 +376,7 @@ describe('platform-server partial hydration integration', () => { expect(ssrContents).toContain('

{ expect(ssrContents).toContain('

{ //

is inside a nested defer block -> different namespace. // expect(ssrContents).toContain('