fix(core): skip hydration for i18n nodes that were not projected (#57356)

This commit fixes an issue that happens when an i18n block is defined as a projectable content, but a parent component doesn't project it. With an extra check added in this commit, the code will be taking a regular "creation" pass instead of attempting hydration.

Resolves #57301.

PR Close #57356
This commit is contained in:
Andrew Kushnir
2024-08-12 18:11:57 -07:00
parent bbc970bb0b
commit de85979648
3 changed files with 63 additions and 7 deletions
+9 -6
View File
@@ -26,6 +26,7 @@ import {IS_I18N_HYDRATION_ENABLED} from './tokens';
import {
getNgContainerSize,
initDisconnectedNodes,
isDisconnectedNode,
isSerializedElementContainer,
processTextNodeBeforeSerialization,
} from './utils';
@@ -407,15 +408,17 @@ function prepareI18nBlockForHydrationImpl(
parentTNode: TNode | null,
subTemplateIndex: number,
) {
if (
!isI18nHydrationSupportEnabled() ||
(parentTNode && isI18nInSkipHydrationBlock(parentTNode))
) {
const hydrationInfo = lView[HYDRATION];
if (!hydrationInfo) {
return;
}
const hydrationInfo = lView[HYDRATION];
if (!hydrationInfo) {
if (
!isI18nHydrationSupportEnabled() ||
(parentTNode &&
(isI18nInSkipHydrationBlock(parentTNode) ||
isDisconnectedNode(hydrationInfo, parentTNode.index - HEADER_OFFSET)))
) {
return;
}
@@ -9,6 +9,7 @@ import {validateMatchingNode, validateNodeExists} from '../../hydration/error_ha
import {locateNextRNode, siblingAfter} from '../../hydration/node_lookup_utils';
import {
getNgContainerSize,
isDisconnectedNode,
markRNodeAsClaimedByHydration,
setSegmentHead,
} from '../../hydration/utils';
@@ -204,7 +205,11 @@ function locateOrCreateElementContainerNode(
): RComment {
let comment: RComment;
const hydrationInfo = lView[HYDRATION];
const isNodeCreationMode = !hydrationInfo || isInSkipHydrationBlock() || isDetachedByI18n(tNode);
const isNodeCreationMode =
!hydrationInfo ||
isInSkipHydrationBlock() ||
isDisconnectedNode(hydrationInfo, index) ||
isDetachedByI18n(tNode);
lastNodeWasCreated(isNodeCreationMode);
@@ -2128,6 +2128,54 @@ describe('platform-server hydration integration', () => {
expect(content.innerHTML).toBe('<span>two</span><div>one</div>');
});
it('should work when i18n content is not projected', async () => {
@Component({
standalone: true,
selector: 'app-content',
template: `
@if (false) {
<ng-content />
}
Content outside of 'if'.
`,
})
class ContentComponent {}
@Component({
standalone: true,
selector: 'app',
template: `
<app-content>
<div i18n>Hello!</div>
<ng-container i18n>Hello again!</ng-container>
</app-content>
`,
imports: [ContentComponent],
})
class SimpleComponent {}
const hydrationFeatures = [withI18nSupport()] as unknown as HydrationFeature<any>[];
const html = await ssr(SimpleComponent, {hydrationFeatures});
const ssrContents = getAppContents(html);
expect(ssrContents).toContain('<app ngh');
resetTViewsFor(SimpleComponent, ContentComponent);
const appRef = await renderAndHydrate(doc, html, SimpleComponent, {hydrationFeatures});
const compRef = getComponentRef<SimpleComponent>(appRef);
appRef.tick();
const clientRootNode = compRef.location.nativeElement;
verifyAllNodesClaimedForHydration(clientRootNode);
verifyClientAndSSRContentsMatch(ssrContents, clientRootNode);
const content = clientRootNode.querySelector('app-content');
const text = content.textContent.trim();
expect(text).toBe("Content outside of 'if'.");
expect(text).not.toContain('Hello');
});
it('should support interleaving projected content', async () => {
@Component({
standalone: true,