feat(router): make route resources reactive and support parallel depe… by atscott · Pull Request #70587 · angular/angular · GitHub
Skip to content

feat(router): make route resources reactive and support parallel depe… - #70587

Draft
atscott wants to merge 2 commits into
angular:mainfrom
atscott:router-resources-reactive
Draft

feat(router): make route resources reactive and support parallel depe…#70587
atscott wants to merge 2 commits into
angular:mainfrom
atscott:router-resources-reactive

Conversation

@atscott

@atscott atscott commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

…ndent loading

Makes ActivatedRoute.resources and ActivatedRouteSnapshot.resources signals (Signal<ResourceResult> | undefined) to enable reactive resource inheritance and parallel execution of dependent resources.

Previously, resources was defined as a static map resolved at navigation time with no inheritance between parent and child routes. This prevented child route resources from depending on parent route resources without introducing sequential waterfalls: if a child resource read ctx.resources(), it would read static data and fail to react when a parent resource completed its fetch. By converting resources to a signal, child resources can reactively read parent resources (params: () => ctx.resources()['parent']?.value()), automatically initiating their own fetch the moment the parent resolves without blocking setup or requiring sequential execution.

This change also addresses several key design requirements:

  • Synchronous pre-pass initialization: Because route setup functions can be asynchronous, child routes could attempt to read ctx.resources() before a parent's setup function had even created the resources property. A synchronous top-down pre-pass (initializeResourceTree) creates the signals and computed inheritance links across all matched routes before any setup functions execute. This ensures that all routes have stable, readable signals from the start.

  • Resource inheritance: Prior to this change, no inheritance was done for route resources. Adds resource inheritance down the route tree, intentionally not using paramsInheritanceStrategy. Unlike URL parameters, route resources represent data that descendant routes and components naturally depend on (similar to route data inheritance). Because resources are reactive signals, child routes can inherit and coordinate with parent resources without introducing sequential waterfall delays.

  • Documentation reorganization: Previously, core architectural topics such as fine-grained signal tracking, parallel execution, and resource inheritance were nested under the "Setup" heading. The guide has been restructured into a clear, logical flow: enabling the feature, defining resources and their context, fine-grained signal reactivity, parallel dependent execution, inheritance, blocking/non-blocking modes, and in-place reloading.

…ndent loading

Makes `ActivatedRoute.resources` and `ActivatedRouteSnapshot.resources` signals (`Signal<ResourceResult> | undefined`) to enable reactive resource inheritance and parallel execution of dependent resources.

Previously, `resources` was defined as a static map resolved at navigation time with no inheritance between parent and child routes. This prevented child route resources from depending on parent route resources without introducing sequential waterfalls: if a child resource read `ctx.resources()`, it would read static data and fail to react when a parent resource completed its fetch. By converting `resources` to a signal, child resources can reactively chain off parent resources (`params: ({chain}) => chain(ctx.resources()['parent'])`), retaining a loading state while the ancestor fetch is in flight and automatically initiating their own fetch the moment the parent resolves without blocking setup or requiring sequential execution.

This change also addresses several key design requirements:

- Synchronous pre-pass initialization:
  Because route setup functions can be asynchronous, child routes could attempt to read `ctx.resources()` before a parent's setup function had even created the resources property. A synchronous top-down pre-pass (`initializeResourceTree`) creates the signals and computed inheritance links across all matched routes before any setup functions execute. This ensures that all routes have stable, readable signals from the start.

- Resource inheritance:
  Prior to this change, no inheritance was done for route resources. Adds resource inheritance down the route tree, intentionally not using `paramsInheritanceStrategy`. Unlike URL parameters, route resources represent data that descendant routes and components naturally depend on (similar to route `data` inheritance). Because resources are reactive signals, child routes can inherit and coordinate with parent resources without introducing sequential waterfall delays.

- Simplified blocking coordination:
  Blocking resources are tracked via per-resource promises that resolve when `hasValueOrResolved()` becomes true (or reject on error). The router coordinates completion simply with `Promise.all(resourceSetupPromises).then(() => Promise.all(blockingResourcePromises))`, eliminating the need for complex coordinator effects.

- Documentation reorganization:
  Previously, core architectural topics such as fine-grained signal tracking, parallel execution, and resource inheritance were nested under the "Setup" heading. The guide has been restructured into a clear, logical flow: enabling the feature, defining resources and their context, fine-grained signal reactivity, parallel dependent execution, inheritance, blocking/non-blocking modes, and in-place reloading.
@atscott atscott added the target: minor This PR is targeted for the next minor release label Sep 6, 2026
@angular-robot angular-robot Bot added detected: feature PR contains a feature commit area: router labels Sep 6, 2026
@ngbot ngbot Bot added this to the Backlog milestone Sep 6, 2026
…n and blocking setup

Streamlines the reactive router resource implementation to reduce bundle size and eliminate redundant operations:

- Merges the separate `initializeResourceTree` traversal pass into the main depth-first `traverse` pass in `setupAndRunResources`. Because `traverse` operates in pre-order (depth-first: parent before children), initializing resource signals at the beginning of route visitation guarantees that ancestor signals exist before child setup functions run.
- Consolidates `runInInjectionContext` around the resource transformation loop in `setupNewRouterResources` rather than entering and exiting injection context per key.
- Simplifies async resolution of route resource definitions to a concise ternary check.
- Extracts `waitForResource` to isolate per-resource promise, effect, and abort tracking from `setupBlocking`, and reuses a single `onDone` callback for both abort signals and route injector destruction.
- Replaces closure callbacks in `updateExistingResources` with a `for...of` loop.
- Condenses the `.reload()` fallback implementation in `routerResource` and adds an early exit in `createResourceOutletBindingEffects` when no route resources exist.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: router detected: feature PR contains a feature commit target: minor This PR is targeted for the next minor release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant