Skip to content

fix(router): prevent child routes from silently dropping when stored component is reattached into a recreated parent shell#69556

Open
arturovt wants to merge 1 commit into
angular:mainfrom
arturovt:fix/router_57285
Open

fix(router): prevent child routes from silently dropping when stored component is reattached into a recreated parent shell#69556
arturovt wants to merge 1 commit into
angular:mainfrom
arturovt:fix/router_57285

Conversation

@arturovt

Copy link
Copy Markdown
Contributor

When a stored component (e.g. EventListCmp at path '') is reattached into a fresh parent shell (one that is destroyed and recreated on every navigation because it is not covered by the reuse strategy), child routes deeper than the stored component could fail to render.

The root cause was in ChildrenOutletContexts.onOutletDeactivated(). It replaced this.contexts with a new empty Map and returned the old one for storage. When the shell was recreated, its fresh router-outlet created a brand-new OutletContext for the stored component, and onOutletReAttached() loaded the saved contexts into that new context's .children. However, the router-outlet inside the stored component (which stayed alive while detached) was injected with the original ChildrenOutletContexts at component creation time and held that reference for its lifetime. Because the original instance's Map had been replaced with an empty one, subsequent activateWith() calls on that outlet created grandchild components with a fresh, empty context. Those grandchildren's outlets registered themselves there, but the router traversed the restored context tree (a different object) and never found an outlet to activate into — so the grandchild component was silently dropped.

Fix: keep the same Map instance in onOutletDeactivated() rather than swapping it out. This ensures that any outlet injected with the original ChildrenOutletContexts and the router's restored context tree both see the same backing Map after reattach. Stale outlet references left behind by destroyed components are already cleaned up naturally through ngOnDestroyonChildOutletDestroyed().

Fixes #57285

…component is reattached into a recreated parent shell

When a stored component (e.g. EventListCmp at path '') is reattached into
a fresh parent shell (one that is destroyed and recreated on every
navigation because it is not covered by the reuse strategy), child routes
deeper than the stored component could fail to render.

The root cause was in `ChildrenOutletContexts.onOutletDeactivated()`. It
replaced `this.contexts` with a new empty Map and returned the old one for
storage. When the shell was recreated, its fresh router-outlet created a
brand-new OutletContext for the stored component, and `onOutletReAttached()`
loaded the saved contexts into *that* new context's `.children`. However,
the router-outlet *inside* the stored component (which stayed alive while
detached) was injected with the *original* `ChildrenOutletContexts` at
component creation time and held that reference for its lifetime. Because
the original instance's Map had been replaced with an empty one, subsequent
`activateWith()` calls on that outlet created grandchild components with a
fresh, empty context. Those grandchildren's outlets registered themselves
there, but the router traversed the *restored* context tree (a different
object) and never found an outlet to activate into — so the grandchild
component was silently dropped.

Fix: keep the same Map instance in `onOutletDeactivated()` rather than
swapping it out. This ensures that any outlet injected with the original
`ChildrenOutletContexts` and the router's restored context tree both see
the same backing Map after reattach. Stale outlet references left behind by
destroyed components are already cleaned up naturally through
`ngOnDestroy` → `onChildOutletDestroyed()`.

Fixes angular#57285
@pullapprove pullapprove Bot requested a review from kirjs June 28, 2026 14:03
@ngbot ngbot Bot added this to the Backlog milestone Jun 28, 2026
@JeanMeche JeanMeche requested review from atscott and removed request for kirjs June 28, 2026 14:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Child routes don't always render on reused parent route

1 participant