fix(core): respect ngSkipHydration on components with projectable nodes in LContainers#68381
Open
sonukapoor wants to merge 2 commits intoangular:mainfrom
Open
fix(core): respect ngSkipHydration on components with projectable nodes in LContainers#68381sonukapoor wants to merge 2 commits intoangular:mainfrom
sonukapoor wants to merge 2 commits intoangular:mainfrom
Conversation
…es in LContainers When a component is created dynamically via ViewContainerRef.createComponent and receives projectable nodes (e.g. raw DOM nodes or embedded view root nodes), applying ngSkipHydration to its host element did not prevent NG0503 from being thrown during SSR serialization. The root cause is an asymmetry in the serialization pipeline. For inline child components, serializeLView already guards the annotateHostElementForHydration call with a ngSkipHydration attribute check, so the component's lView is never serialized when hydration is opted out. For components hosted inside an LContainer (created via ViewContainerRef.createComponent), serializeLContainer called serializeLView unconditionally — bypassing that guard entirely. When serializeLView then encountered a projection slot backed by a raw DOM node array, it threw NG0503 regardless of the ngSkipHydration flag. The fix adds the same guard inside serializeLContainer before calling serializeLView: if the child lView belongs to a component whose host element carries ngSkipHydration, the lView serialization is skipped. This matches the existing behavior for inline components and allows the documented workaround to actually work for dynamically created ones. Fixes angular#67928
thePunderWoman
requested changes
Apr 27, 2026
| (childHostElement as HTMLElement).hasAttribute(SKIP_HYDRATION_ATTR_NAME) | ||
| ) { | ||
| // Component is skipped — nothing to serialize for its lView. | ||
| } else { |
Contributor
There was a problem hiding this comment.
Can we invert this logic so that we don't have an empty if block? That's much easier to follow.
Contributor
Author
There was a problem hiding this comment.
Oops, I thought I had done that already. I have inverted the condition. The serializeLView call now lives in the positive branch, and the empty block is gone.
Inverts the condition in serializeLContainer so the serialization call sits in the positive branch rather than an empty true-block with the work deferred to else. Logic is unchanged.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PR Checklist
PR Type
What is the current behavior?
Issue Number: #67928
When a component is created dynamically via
ViewContainerRef.createComponentand receives projectable nodes, applyingngSkipHydrationto its host element does not preventNG0503from being thrown during SSR serialization. The error fires even though the developer has followed the documented workaround.What is the new behavior?
ngSkipHydrationis now respected for components hosted inside anLContainer. When such a component's host element carries thengSkipHydrationattribute,serializeLContainerskips callingserializeLViewfor it — the same wayserializeLViewalready skips inline child components withngSkipHydration.Root cause
There is an asymmetry in the serialization pipeline between inline components and dynamically created ones:
Inline components (
Array.isArray(lView[i])branch inserializeLView): before callingannotateHostElementForHydration, the code checks whether the host element hasngSkipHydrationand bails out early if so. The component's lView is never serialized.LContainer-hosted components (
serializeLContainer):serializeLViewwas called unconditionally with no equivalent guard. WhenserializeLViewlater walked the component's projection slots and found a raw DOM node array (the projectable nodes), it threwNG0503— regardless of whetherngSkipHydrationwas present.The fix adds the missing guard in
serializeLContainer: before callingserializeLViewfor a component lView, check whether the host element opts out of hydration, and skip if it does.Does this PR introduce a breaking change?
Other information
A regression test was added that reproduces the original failure: a component decorated with
ngSkipHydrationreceives a raw DOM node as a projectable node viaViewContainerRef.createComponent. Before this fix the test threwNG0503; after the fix SSR completes successfully and the component is present in the output.