Skip to content

Resource hasValue() is false for a moment when using TransferState cached data, causing hydration to fail #62897

Description

@zip-fa

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

core

Is this a regression?

No

Description

A minimal reproduction demonstrating hydration issues when using @else and @empty blocks with the control flow syntax. The app fetches Pokemon data (httpClient + rxResource + transfer state enabled) and displays three scenarios:

  1. Works: Uses separate @if blocks - hydration works correctly
Image
  1. Broken: Uses @if/@else blocks - hydration fails
Image
  1. Broken: Uses @for/@empty blocks - hydration fails
Image

Expected: All components should hydrate properly
Actual: When using @else or @empty, components are not hydrating

Please provide a link to a minimal reproduction of the bug

https://github.com/zip-fa/ng20-new-hydration-issue

Please provide the exception or error you saw


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

ng version

     _                      _                 ____ _     ___
    / \   _ __   __ _ _   _| | __ _ _ __     / ___| |   |_ _|
   / △ \ | '_ \ / _` | | | | |/ _` | '__|   | |   | |    | |
  / ___ \| | | | (_| | |_| | | (_| | |      | |___| |___ | |
 /_/   \_\_| |_|\__, |\__,_|_|\__,_|_|       \____|_____|___|
                |___/
    

Angular CLI: 20.1.3
Node: 24.2.0
Package Manager: npm 11.3.0
OS: darwin arm64

Angular: 20.1.3
... build, cli, common, compiler, compiler-cli, core, forms
... platform-browser, platform-server, router, ssr

Package                      Version
------------------------------------------------------
@angular-devkit/architect    0.2001.3
@angular-devkit/core         20.1.3
@angular-devkit/schematics   20.1.3
@schematics/angular          20.1.3
rxjs                         7.8.2
typescript                   5.8.3

Anything else?

No response

Activity

  1. added this to the needsTriage milestone on Jul 30, 2025
  2. thePunderWoman commented on Jul 30, 2025

    @thePunderWoman
    Contributor

    So preliminarily, what I see is that the Resource is what's causing the problem. The hasValue check is returning false first, which in the case of no @else, the data was serialized so it can safely hydrate what's there. With the @else and the @empty, what's happening is Angular first renders the @else case and that gets hydrated, and then the hasValue() returns true, which means the @if gets rendered fresh. The same thing is happening with the @empty. So I think this is timing with the rxResource. The data does get properly serialized in the transfer state, but for some reason the Resource says false for data. So I think this is actually a resource bug, and not a hydration bug.

  3. thePunderWoman commented on Jul 30, 2025

    @thePunderWoman
    Contributor

    It could also be Zoneless related with change detection timing and microtasks. I'll look into this more. @atscott may have thoughts.

  4. changed the title [-]Hydration Fails with @else and @empty Control Flow Blocks[/-] [+]Resource `hasValue()` is false for a moment when using TransferState cached data, causing hydration to fail[/+] on Aug 4, 2025
  5. StriplThund commented on Aug 14, 2025

    @StriplThund

    We encountered the same bug, tried to reproduce it with the whole team until we opened Github and found the same issue. Are there any chances that this will be fixed before the 20.2 release? Thank you!

  6. popeye88 commented on Aug 22, 2025

    @popeye88

    same here, could it be fixed please? it makes hydration with rxResource completely useless

  7. Raume commented on Aug 22, 2025

    @Raume

    just encountered the same bug
    it is very annoying that team ignores bug which breaks one the biggest SSR features

  8. zip-fa commented on Aug 26, 2025

    @zip-fa
    Author

    Hey team Angular. I understand your hard work and release schedule, but i’ll leave a comment again to make sure this issue is not lost in the list.
    Thanks for your work!

  9. 19 remaining items

  10. Raume commented on Mar 31, 2026

    @Raume

    Any chances fix will land in v22?

  11. added a commit that references this issue on Apr 15, 2026
    d007911
  12. zip-fa commented on Apr 17, 2026

    @zip-fa
    Author

    Hey.
    I've updated the repro with new case: https://github.com/zip-fa/ng20-new-hydration-issue
    While other cases were resolved by your commit, Angular (22.0.0-next.8) still struggles to hydrate with one particular case: nested router-outlet.

    Image

    ^ Confirming your fix works for single router-outlet

    Reproduction: navigate to /broken-wrapper. The route tree is a parent component: BrokenWrapper (which renders its own ) with a child route that renders BrokenWrapperHome — a component using rxResource + @if (hasValue()) { ... } @else { ... }. Hydration breaks (DOM is destroyed and re-created, event replay is lost).

    Image Image

    What exactly breaks:

    {
        path: 'broken-wrapper',
        component: BrokenWrapper,
        children: [
          {
            path: '',
            loadComponent: async () => import('./broken-wrapper-home')
          }
        ]
      },

    Notable observations:

    • Inlining the same child component directly into the wrapper's template (no nested ) hydrates correctly.
    • Switching the child route from loadComponent to eager component: does not fix it, so this isn't about the lazy-import boundary - it reproduces even when the child class is available synchronously.
    • Removing only the @else branch makes hydration work, matching the pattern from the original issue.

    My suspicion is that the sync TransferState path relies on the resource being constructed inside the initial hydration window. Because instantiates its target imperatively through the router pipeline (which settles across at least one microtask even for eager routes), the child component's rxResource field initializer runs after that window has closed. hasValue() is therefore false on the child's first CD, Angular picks the @else branch, and that diverges from the server-rendered @if branch → hydration mismatch.

    Happy to adjust the repro or test a candidate fix if helpful.

  13. Raume commented on Apr 21, 2026

    @Raume

    Hey. I've updated the repro with new case: https://github.com/zip-fa/ng20-new-hydration-issue While other cases were resolved by your commit, Angular (22.0.0-next.8) still struggles to hydrate with one particular case: nested router-outlet.

    Image ^ Confirming your fix works for single router-outlet

    Reproduction: navigate to /broken-wrapper. The route tree is a parent component: BrokenWrapper (which renders its own ) with a child route that renders BrokenWrapperHome — a component using rxResource + @if (hasValue()) { ... } @else { ... }. Hydration breaks (DOM is destroyed and re-created, event replay is lost).

    Image Image
    What exactly breaks:

    {
    path: 'broken-wrapper',
    component: BrokenWrapper,
    children: [
    {
    path: '',
    loadComponent: async () => import('./broken-wrapper-home')
    }
    ]
    },
    Notable observations:

    • Inlining the same child component directly into the wrapper's template (no nested ) hydrates correctly.
    • Switching the child route from loadComponent to eager component: does not fix it, so this isn't about the lazy-import boundary - it reproduces even when the child class is available synchronously.
    • Removing only the @else branch makes hydration work, matching the pattern from the original issue.

    My suspicion is that the sync TransferState path relies on the resource being constructed inside the initial hydration window. Because instantiates its target imperatively through the router pipeline (which settles across at least one microtask even for eager routes), the child component's rxResource field initializer runs after that window has closed. hasValue() is therefore false on the child's first CD, Angular picks the @else branch, and that diverges from the server-rendered @if branch → hydration mismatch.

    Happy to adjust the repro or test a candidate fix if helpful.

    Isn’t this a concern of separate issue? @JeanMeche

  14. added a commit that references this issue on Apr 21, 2026
    eb3121d
  15. zip-fa commented on May 7, 2026

    @zip-fa
    Author

    @alxhub hi. You closed the issue, however it still reproduces with the case stated above. Should i create a separate issue for nested router-outlet hydration bug?

  16. JeanMeche commented on May 7, 2026

    @JeanMeche
    Member

    Which version did you tried this on ? Did you enable the hydration id, for transfer state caching ?

  17. zip-fa commented on May 8, 2026

    @zip-fa
    Author

    Which version did you tried this on ? Did you enable the hydration id, for transfer state caching ?

    https://github.com/zip-fa/ng20-new-hydration-issue
    Repro is on next.11

    What do you mean hydration id for transer state caching? Repro uses transfer state + rxjs http client which does that automatically by default.

    I also stated previously that sync cached value fix helped only partially - it fixes single router-outlet case, but not the nested one, which breaks as it was in initial message of this issue

  18. zip-fa commented on May 11, 2026

    @zip-fa
    Author

    Which version did you tried this on ? Did you enable the hydration id, for transfer state caching ?

    Do you need any other context?

  19. angular-automatic-lock-bot commented on Jun 11, 2026

    @angular-automatic-lock-bot

    This issue has been automatically locked due to inactivity.
    Please file a new issue if you are encountering a similar or related problem.

    Read more about our automatic conversation locking policy.

    This action has been performed automatically by a bot.

  20. locked and limited conversation to collaborators on Jun 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions