Skip to content

refactor(router): add support for non-blocking router resources - #70211

Open
atscott wants to merge 3 commits into
angular:mainfrom
atscott:resourcestack1
Open

refactor(router): add support for non-blocking router resources #70211
atscott wants to merge 3 commits into
angular:mainfrom
atscott:resourcestack1

Conversation

@atscott

@atscott atscott commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Introduces support for route-level resources via the resources property on route definitions, enabled with withRouterResources().

Router resources provide a reactive, signal-based alternative to resolvers, allowing routes to declare resources tied to route parameters and route lifecycle:

  • The resources function executes during navigation transitions within an injection context scoped to the route (_localInjector).
  • It receives a ResourceContext containing signals for params, queryParams, fragment, and data, alongside the static snapshot.
  • For newly created routes, resources runs once and attaches to ActivatedRoute.resources. For reused routes, parameter signals update reactively to trigger new data fetches while keeping resource references stable.
  • Wrapped resources (routerResource) provide transactional stability: snapshots are frozen during active navigations to prevent UI jitter, unfreezing on NavigationEnd.
  • On cancelled navigations or errors, rollback recovery retains the frozen snapshot until reverted signals settle, avoiding flashes of loading state. Manual reloads are rejected while frozen.
  • Local injectors are automatically cleaned up if navigations are cancelled or rolled back.

Note that this commit only implements non-blocking resources (marked with nonBlocking()), deferring blocking resource resolution to future work to keep the initial changeset smaller and less complex.

@ngbot ngbot Bot added this to the Backlog milestone Aug 14, 2026
@atscott
atscott force-pushed the resourcestack1 branch 4 times, most recently from f1a4e5f to 45c2c1f Compare August 14, 2026 20:05
… feature

Activated Route Injector feature was developed for resources. This moves the internal
naming (files and vars) to reflect this now that we have landed the core bits of plumbing
and are ready to implement router resources.
@atscott
atscott force-pushed the resourcestack1 branch 4 times, most recently from 7308452 to 328a20f Compare August 17, 2026 17:54
@atscott

atscott commented Aug 17, 2026

Copy link
Copy Markdown
Contributor Author

On the size diff, this is mostly a result of accumulated size increase and crossing the threshold now

origin/main router size (minified): 81,628 bytes
HEAD router size (minified): 82,078 bytes
Difference: +450 bytes (~0.5%)

Introduces support for route-level resources via the `resources` property on route definitions, enabled with `withRouterResources()`.

Router resources provide a reactive, signal-based alternative to resolvers, allowing routes to declare resources tied to route parameters and route lifecycle:
- The `resources` function executes during navigation transitions within an injection context scoped to the route (`_localInjector`).
- It receives a `ResourceContext` containing signals for `params`, `queryParams`, `fragment`, and `data`, alongside the static `snapshot`.
- For newly created routes, `resources` runs once and attaches to `ActivatedRoute.resources`. For reused routes, parameter signals update reactively to trigger new data fetches while keeping resource references stable.
- Wrapped resources (`routerResource`) provide transactional stability: snapshots are frozen during active navigations to prevent UI jitter, unfreezing on `NavigationEnd`.
- On cancelled navigations or errors, rollback recovery retains the frozen snapshot until reverted signals settle, avoiding flashes of loading state. Manual reloads are rejected while frozen.
- Local injectors are automatically cleaned up if navigations are cancelled or rolled back.

Note that this commit only implements non-blocking resources (marked with `nonBlocking()`), deferring blocking resource resolution to future work to keep the initial changeset smaller and less complex.
@atscott
atscott marked this pull request as ready for review August 17, 2026 20:51
@atscott
atscott requested a review from leonsenft August 17, 2026 20:51
@pullapprove
pullapprove Bot requested a review from JeanMeche August 17, 2026 20:51
Comment thread goldens/public-api/router/index.api.md Outdated
// (undocumented)
get queryParamMap(): ParamMap;
queryParams: Params;
resources?: ResourceResult;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're missing an export for ResourceResult, it should appear in this file.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is exported just not to public API yet, which is correct.

@atscott atscott Aug 17, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh wait this is ActivatedRouteSnapshot - I thought it was Route. This needs to be internal

@pullapprove
pullapprove Bot requested a review from JeanMeche August 17, 2026 20:53
Comment thread packages/router/src/models.ts
@pullapprove
pullapprove Bot requested review from JeanMeche August 17, 2026 20:54
Comment thread packages/router/src/provide_router.ts
// reactively loading new parameters behind the scenes) would disrupt the router's resource
// tracking for the transition. Similarly, during a rollback recovery, the router is
// already managing the resource reload to restore the previous state.
if (frozenSnapshot() !== null) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we untrack here ?

Suggested change
if (untracked(frozenSnapshot) !== null) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The entire navigation transition is untracked (and async)

@pullapprove
pullapprove Bot requested a review from JeanMeche August 17, 2026 23:08
}
}

return Promise.all(resourceSetupPromises).then(throwIfAborted);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need .then(throwIfAborted); ?

Wouldn't takeUntil(abortSignalToObservable(abortController.signal take care of unsubscribing and we would end up with a UnhandledPromiseRejection ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll remove it - it's because there's a blocking resolver await after that part which isn't included here. There's no unhandled promise because it's handled by rxjs.

@pullapprove
pullapprove Bot requested a review from JeanMeche August 17, 2026 23:16
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.

2 participants