Skip to content

ViewContainerRef obtained via template reference variable is incompatible with client hydration #68014

@nsbarsukov

Description

@nsbarsukov

Which @angular/* package(s) are the source of the bug?

core, platform-browser

Is this a regression?

No

Description

When provideClientHydration() is enabled, dynamically inserting a component into a ViewContainerRef obtained via a template reference variable (#vcr + viewChild('vcr', {read: ViewContainerRef})) throws a hydration error.

Error:

ERROR Error: ASSERTION ERROR: Unexpected state: no hydration info available for a given TNode, which represents a view container. [Expected=> null != undefined <=Actual]
    at throwError2 (_effect-chunk2.mjs:232:9)
    at assertDefined (_effect-chunk2.mjs:228:5)
    at populateDehydratedViewsInLContainerImpl (_debug_node-chunk.mjs:9097:16)
    at locateOrCreateAnchorNode (_debug_node-chunk.mjs:9108:8)
    at createContainerRef (_debug_node-chunk.mjs:9059:3)
    at createSpecialToken (_debug_node-chunk.mjs:9350:12)
    at createResultForNode (_debug_node-chunk.mjs:9338:12)
    at materializeViewResults (_debug_node-chunk.mjs:9368:21)
    at getQueryResults (_debug_node-chunk.mjs:9462:89)
    at refreshSignalQuery (_debug_node-chunk.mjs:9507:19)

Reproduction

Clone this repo https://github.com/nsbarsukov/ng-bug-view-container-ref-hydration

npm ci
npm start

Open http://localhost:4200 in the browser, open DevTools console, and click the button.

Minimal reproduction code

Application with enabled hydration!

@Component({
  selector: 'app-root',
  template: `
    <button (click)="insert()">Insert</button>
    <div #portalHost></div>
  `,
})
export class App {
  vcr = viewChild.required('portalHost', {read: ViewContainerRef});

  insert() {
    this.vcr().createComponent(AnyDynamicComponent); // crashes with hydration error
  }
}

Workaround

Use a directive to obtain the ViewContainerRef via inject() instead of a template reference variable:

@Directive({selector: '[portalAnchor]'})
export class PortalAnchor {
  vcr = inject(ViewContainerRef);
}

@Component({
  imports: [PortalAnchor],
  template: `
    <button (click)="insert()">Insert</button>
    <div portalAnchor></div>
  `,
})
export class App {
  anchor = viewChild.required(PortalAnchor);

  insert() {
    this.anchor().vcr.createComponent(AnyDynamicComponent); // works fine
  }
}

Please provide a link to a minimal reproduction of the bug

https://github.com/nsbarsukov/ng-bug-view-container-ref-hydration

Please provide the exception or error you saw

ERROR Error: ASSERTION ERROR: Unexpected state: no hydration info available for a given TNode, which represents a view container. [Expected=> null != undefined <=Actual]
    at throwError2 (_effect-chunk2.mjs:232:9)
    at assertDefined (_effect-chunk2.mjs:228:5)
    at populateDehydratedViewsInLContainerImpl (_debug_node-chunk.mjs:9097:16)
    at locateOrCreateAnchorNode (_debug_node-chunk.mjs:9108:8)
    at createContainerRef (_debug_node-chunk.mjs:9059:3)
    at createSpecialToken (_debug_node-chunk.mjs:9350:12)
    at createResultForNode (_debug_node-chunk.mjs:9338:12)
    at materializeViewResults (_debug_node-chunk.mjs:9368:21)
    at getQueryResults (_debug_node-chunk.mjs:9462:89)
    at refreshSignalQuery (_debug_node-chunk.mjs:9507:19)

Please provide the environment you discovered this bug in (run ng version)

Angular CLI       : 21.2.6
Angular           : 21.2.7
Node.js           : 24.12.0
Package Manager   : npm 11.6.2
Operating System  : darwin arm64

┌───────────────────────────┬───────────────────┬───────────────────┐
│ Package                   │ Installed Version │ Requested Version │
├───────────────────────────┼───────────────────┼───────────────────┤
│ @angular/build            │ 21.2.6            │ ^21.2.6           │
│ @angular/cli              │ 21.2.6            │ ^21.2.6           │
│ @angular/common           │ 21.2.7            │ ^21.2.0           │
│ @angular/compiler         │ 21.2.7            │ ^21.2.0           │
│ @angular/compiler-cli     │ 21.2.7            │ ^21.2.0           │
│ @angular/core             │ 21.2.7            │ ^21.2.0           │
│ @angular/platform-browser │ 21.2.7            │ ^21.2.0           │
│ @angular/platform-server  │ 21.2.7            │ ^21.2.0           │
│ @angular/ssr              │ 21.2.6            │ ^21.2.6           │
│ rxjs                      │ 7.8.2             │ ~7.8.0            │
│ typescript                │ 5.9.3             │ ~5.9.2            │
└───────────────────────────┴───────────────────┴───────────────────┘

Anything else?

Real-world example

Explore this bug report inside Taiga UI Repository:

and then explore our workaround:

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions