Skip to content

Hydration fails with rxResource + @else inside nested <router-outlet> #68931

Open

Description

@zip-fa

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

core, router

Is this a regression?

No

Description

Follow-up from #62897. The fix landed in #68226 resolved the httpResource hydration mismatch for components rendered in a single <router-outlet>, but the bug still reproduces when the component using rxResource + @if/@else is rendered inside a nested <router-outlet>.

Setup:

  • A parent route renders BrokenWrapper, which has its own <router-outlet />
  • A child route renders BrokenWrapperHome inside that nested outlet
  • BrokenWrapperHome uses an rxResource (via a root-provided service calling httpClient.get()) and renders with @if (resource.hasValue()) { ... } @else { ... }
  • SSR with RenderMode.Server, zoneless
// Route config
{
  path: 'broken-wrapper',
  component: BrokenWrapper,
  children: [
    {
      path: '',
      loadComponent: async () => import('./broken-wrapper-home')
    }
  ]
}
// BrokenWrapper — just a shell with a nested outlet
@Component({
  template: `<router-outlet />`
})
export default class BrokenWrapper {}
// BrokenWrapperHome — child component with rxResource
@Component({
  template: `
    @if (pokemonService.pokemons.hasValue()) {
      <!-- render data -->
    } @else {
      Hello!
    }
  `
})
export default class BrokenWrapperHome {
  pokemonService = inject(PokemonService);
}
// PokemonService
@Injectable({ providedIn: 'root' })
export class PokemonService {
  private readonly http = inject(HttpClient);

  public readonly pokemons = rxResource({
    stream: () => this.http.get<PokemonResponse>('https://pokeapi.co/api/v2/pokemon?limit=20')
  });
}

Expected: The child component hydrates correctly — the server-rendered @if branch is preserved.

Actual: Hydration fails. The DOM is destroyed and re-created, event replay is lost. Angular renders the @else branch on the client's first change detection, diverging from the server-rendered @if branch.

Key observations:

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)

22-next.11

Anything else?

Navigate to /broken-wrapper or /broken-wrapper-eager to reproduce.

Activity

  1. added this to the needsTriage milestone on May 26, 2026
  2. self-assigned this
    on May 26, 2026
  3. JeanMeche commented on May 27, 2026

    @JeanMeche
    Member

    This is an interesting illustration of effect scheduling (the resource is to the effect execution).
    I would need to get to the bottom of this but

    • In the base case, the effect runs before the first time the component is checked : The httpClient fire and the transfer cache returns synchronously its value
    • In the nested route, the effect fires only after the component is first checked : the resource is still loading and the DOM is trashed on bootstrap/hydration and then recreated when the resource has its value.

    The workarounds today are :

    • ensure we store/read the cache synchronously independently from effect scheduling by setting an id on the resource but that would result in duplicated caching (HttpTransferCache by the HttpClient and the resource caching it by itself).
    • use httpResource directly which also doesn't realy on effect to read the cache.
  4. zip-fa commented on May 27, 2026

    @zip-fa
    Author

    In fact, that means that rxResource in its most common case (http client requests) is unusable in SSR env. Maybe there should be an integration between rxResource <-> http client calls to automatically manage the cache?

  5. JeanMeche commented on May 27, 2026

    @JeanMeche
    Member

    In those case i would suggest: disabling the http caching with transferCache:false on the request itself and setting the rxResource id.

  6. zip-fa commented on May 27, 2026

    @zip-fa
    Author

    yeah i understand the flow, this is kinda... frustrating. This will force us (and others) to use only rxResource OR http client caching, because there might be places where http client is called directly, and other places where rxResource is being used. What is the official way from the team to handle this? Will there be a fix, or this will be a documented known limitation with workarounds?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area: coreIssues related to the framework runtimecore: hydrationcross-cutting: resourceIssues related to the newly introduced resource / httpResource

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions