Repository navigation
Open
Labels
area: coreIssues related to the framework runtimeIssues related to the framework runtimecore: hydrationcross-cutting: resourceIssues related to the newly introduced resource / httpResourceIssues related to the newly introduced resource / httpResource
Milestone
Description
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
BrokenWrapperHomeinside that nested outlet BrokenWrapperHomeuses anrxResource(via a root-provided service callinghttpClient.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:
- Inlining the same component directly into the parent template (no nested
<router-outlet>) hydrates correctly - Switching from
loadComponent(lazy) tocomponent:(eager) does not fix it — the issue is not about the lazy-import boundary - Removing the
@elsebranch makes hydration work (same pattern as ResourcehasValue()is false for a moment when using TransferState cached data, causing hydration to fail #62897) - The same component with the same
rxResourcehydrates fine when rendered via the top-level<router-outlet>
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.
Reacted by Sergei Zheleshchenko
Metadata
Metadata
Assignees
Labels
area: coreIssues related to the framework runtimeIssues related to the framework runtimecore: hydrationcross-cutting: resourceIssues related to the newly introduced resource / httpResourceIssues related to the newly introduced resource / httpResource
Activity
JeanMeche commentedon May 27, 2026
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
effectruns before the first time the component is checked : ThehttpClientfire and the transfer cache returns synchronously its valueeffectfires only after the component is first checked : the resource is stillloadingand the DOM is trashed on bootstrap/hydration and then recreated when the resource has its value.The workarounds today are :
effectscheduling by setting anidon the resource but that would result in duplicated caching (HttpTransferCache by the HttpClient and the resource caching it by itself).httpResourcedirectly which also doesn't realy oneffectto read the cache.zip-fa commentedon May 27, 2026
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?
JeanMeche commentedon May 27, 2026
In those case i would suggest: disabling the http caching with
transferCache:falseon the request itself and setting therxResourceid.zip-fa commentedon May 27, 2026
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?