{{ message }}
feat(router): make route resources reactive and support parallel depe… - #70587
Draft
atscott wants to merge 2 commits into
Draft
feat(router): make route resources reactive and support parallel depe…#70587atscott wants to merge 2 commits into
atscott wants to merge 2 commits into
Conversation
…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.
…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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

…ndent loading
Makes
ActivatedRoute.resourcesandActivatedRouteSnapshot.resourcessignals (Signal<ResourceResult> | undefined) to enable reactive resource inheritance and parallel execution of dependent resources.Previously,
resourceswas 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 readctx.resources(), it would read static data and fail to react when a parent resource completed its fetch. By convertingresourcesto 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 routedatainheritance). 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.