fix(core): handle hydration of components that project content conditionally (#57383)

This commit fixes an issue when hydration serialization tries to calculate DOM path to a content projection node (`<ng-content>`), but such nodes do not have DOM representation.

Resolves #56750.

PR Close #57383
This commit is contained in:
Andrew Kushnir 2024-08-13 18:07:52 -07:00 committed by Dylan Hunn
parent 751880a16a
commit d4449fce21
2 changed files with 72 additions and 0 deletions

View file

@ -583,6 +583,12 @@ function conditionallyAnnotateNodePath(
lView: LView<unknown>,
excludedParentNodes: Set<number> | null,
) {
if (isProjectionTNode(tNode)) {
// Do not annotate projection nodes (<ng-content />), since
// they don't have a corresponding DOM node representing them.
return;
}
// Handle case #1 described above.
if (
tNode.projectionNext &&

View file

@ -5685,6 +5685,72 @@ describe('platform-server hydration integration', () => {
verifyClientAndSSRContentsMatch(ssrContents, clientRootNode);
});
it('should support cases when some element nodes are not projected', async () => {
@Component({
standalone: true,
selector: 'app-dropdown-content',
template: `<ng-content />`,
})
class DropdownContentComponent {}
@Component({
standalone: true,
selector: 'app-dropdown',
template: `
@if (false) {
<ng-content select="app-dropdown-content" />
}
`,
})
class DropdownComponent {}
@Component({
standalone: true,
imports: [DropdownComponent, DropdownContentComponent],
selector: 'app-menu',
template: `
<app-dropdown>
<app-dropdown-content>
<ng-content />
</app-dropdown-content>
</app-dropdown>
`,
})
class MenuComponent {}
@Component({
selector: 'app',
standalone: true,
imports: [MenuComponent],
template: `
<app-menu>
Menu Content
</app-menu>
`,
})
class SimpleComponent {}
const html = await ssr(SimpleComponent);
const ssrContents = getAppContents(html);
expect(ssrContents).toContain('<app ngh');
resetTViewsFor(
SimpleComponent,
MenuComponent,
DropdownComponent,
DropdownContentComponent,
);
const appRef = await renderAndHydrate(doc, html, SimpleComponent);
const compRef = getComponentRef<SimpleComponent>(appRef);
appRef.tick();
const clientRootNode = compRef.location.nativeElement;
verifyAllNodesClaimedForHydration(clientRootNode);
verifyClientAndSSRContentsMatch(ssrContents, clientRootNode);
});
it('should support cases when view containers are not projected', async () => {
@Component({
standalone: true,