Skip to content

Commit

Permalink
fix(core): skip hydration for i18n nodes that were not projected
Browse files Browse the repository at this point in the history
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 angular#57301.
  • Loading branch information
AndrewKushnir committed Aug 13, 2024
1 parent d608b85 commit 94744ae
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 7 deletions.
15 changes: 9 additions & 6 deletions packages/core/src/hydration/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {IS_I18N_HYDRATION_ENABLED} from './tokens';
import {
getNgContainerSize,
initDisconnectedNodes,
isDisconnectedNode,
isSerializedElementContainer,
processTextNodeBeforeSerialization,
} from './utils';
Expand Down Expand Up @@ -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;
}

Expand Down
7 changes: 6 additions & 1 deletion packages/core/src/render3/instructions/element_container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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);

Expand Down
48 changes: 48 additions & 0 deletions packages/platform-server/test/hydration_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit 94744ae

Please sign in to comment.