mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
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:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user