Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions goldens/public-api/router/index.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { Provider } from '@angular/core';
import { ProviderToken } from '@angular/core';
import { QueryList } from '@angular/core';
import { Renderer2 } from '@angular/core';
import { Resource } from '@angular/core';
import { Signal } from '@angular/core';
import { SimpleChanges } from '@angular/core';
import { Title } from '@angular/platform-browser';
Expand Down Expand Up @@ -71,6 +72,7 @@ export class ActivatedRouteSnapshot {
// (undocumented)
get queryParamMap(): ParamMap;
queryParams: Params;
resources?: ResourceResult;
get root(): ActivatedRouteSnapshot;
readonly routeConfig: Route | null;
get title(): string | undefined;
Expand Down
1 change: 1 addition & 0 deletions goldens/public-api/router/testing/index.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { Provider } from '@angular/core';
import { ProviderToken } from '@angular/core';
import { QueryList } from '@angular/core';
import { Renderer2 } from '@angular/core';
import { Resource } from '@angular/core';
import { Signal } from '@angular/core';
import { SimpleChanges } from '@angular/core';
import { Type } from '@angular/core';
Expand Down
4 changes: 2 additions & 2 deletions packages/core/test/bundling/router/bundle.golden_symbols.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
{
"chunks": {
"main": [
"ACTIVATED_ROUTE_INJECTOR_FEATURE",
"AFTER_RENDER_SEQUENCES_TO_ADD",
"ANIMATIONS",
"ANIMATION_QUEUE",
Expand Down Expand Up @@ -243,6 +242,7 @@
"ROUTER_CONFIGURATION",
"ROUTER_OUTLET_DATA",
"ROUTER_PRELOADER",
"ROUTER_RESOURCES_FEATURE",
"ROUTER_SCROLLER",
"ROUTES",
"ROUTES2",
Expand Down Expand Up @@ -584,7 +584,6 @@
"diPublicInInjector",
"directiveHostEndFirstCreatePass",
"directiveHostFirstCreatePass",
"discardNewActivatedRoutes",
"documentSupported",
"domOnlyFirstCreatePass",
"elementAttributeInternal",
Expand Down Expand Up @@ -1045,6 +1044,7 @@
"resolveNode",
"retrieveHydrationInfo",
"reusedNodes",
"rollbackState",
"rootRoute",
"runAfterLeaveAnimations",
"runCanActivate",
Expand Down
19 changes: 0 additions & 19 deletions packages/router/src/activated_route_injector_feature.ts

This file was deleted.

5 changes: 3 additions & 2 deletions packages/router/src/create_router_state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ function createNode(
// reuse an activated route that is currently displayed on the screen
if (prevState && routeReuseStrategy.shouldReuseRoute(curr.value, prevState.value.snapshot)) {
const value = prevState.value;
value._futureSnapshot = curr.value;
value._setPending(curr.value);
const children = createOrReuseChildren(routeReuseStrategy, curr, prevState, newlyCreatedRoutes);
return new TreeNode<ActivatedRoute>(value, children);
} else {
Expand All @@ -50,7 +50,7 @@ function createNode(
const detachedRouteHandle = routeReuseStrategy.retrieve(curr.value);
if (detachedRouteHandle !== null) {
const tree = (detachedRouteHandle as DetachedRouteHandleInternal).route;
tree.value._futureSnapshot = curr.value;
tree.value._setPending(curr.value);
tree.children = curr.children.map((c) =>
createNode(routeReuseStrategy, c, undefined, newlyCreatedRoutes),
);
Expand All @@ -59,6 +59,7 @@ function createNode(
}

const value = createActivatedRoute(curr.value);
value._setPending(curr.value);
newlyCreatedRoutes.add(value);
const children = curr.children.map((c) =>
createNode(routeReuseStrategy, c, undefined, newlyCreatedRoutes),
Expand Down
57 changes: 57 additions & 0 deletions packages/router/src/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,63 @@ import {
NgModuleFactory,
Provider,
ProviderToken,
Signal,
Type,
Resource,
} from '@angular/core';
import {Observable} from 'rxjs';
export {DefaultExport} from '@angular/core';

import type {ActivatedRouteSnapshot, RouterStateSnapshot} from './router_state';
import {ParamMap, Params} from './shared';
import type {UrlSegment, UrlSegmentGroup, UrlTree} from './url_tree';

/**
* The expected return type of a `resources` function.
* @experimental
*/
export type ResourceResult = Record<string, Resource<unknown>>;

// Developer notes: properties are exposed as a plain Record (`Params`) rather than a `ParamMap`
// to allow future type-check layers to infer exact keys (e.g., `{ id: string }`).
// Same applies to data and queryparams.
/**
* The contextual information provided to a `resources` function.
* @experimental
*/
export interface ResourceContext {
/**
* The matrix parameters of the route.
*
* @experimental
*/
params: Signal<Params>;
/**
* The query parameters of the route.
*
* @experimental
*/
queryParams: Signal<Params>;
/**
* The URL fragment.
* @experimental
*/
fragment: Signal<string | null>;
/**
* Data provided in the route configuration.
*
* @experimental
*/
data: Signal<Record<string, any>>;
/**
* The static activated route snapshot for this navigation.
* Useful for reading initial static configuration statically without
* reacting to future parameter changes on reused routes.
* @experimental
*/
snapshot: ActivatedRouteSnapshot;
}

/**
* How to handle a navigation request to the current URL. One of:
*
Expand Down Expand Up @@ -713,6 +762,14 @@ export interface Route {
* @see [Resolve](guide/routing/data-resolvers#what-are-data-resolvers)
*/
resolve?: ResolveData;
/**
* A function that returns a map of resources.
* This function is executed during the Main Loading Phase of a navigation.
* @experimental
* @internal
*/
resources?: (ctx: ResourceContext) => ResourceResult | Promise<ResourceResult>;

/**
* An array of child `Route` objects that specifies a nested route
* configuration.
Expand Down
37 changes: 24 additions & 13 deletions packages/router/src/navigation_transition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ import {ActivateRoutes} from './operators/activate_routes';
import {checkGuards} from './operators/check_guards';
import {recognize} from './operators/recognize';
import {resolveData} from './operators/resolve_data';
import {ROUTER_RESOURCES_FEATURE} from './router_resource_feature';
import {switchTap} from './operators/switch_tap';
import {TitleStrategy} from './page_title_strategy';
import type {Router} from './router';
Expand All @@ -83,7 +84,7 @@ import {UrlSerializer, UrlTree} from './url_tree';
import {abortSignalToObservable} from './utils/abort_signal_to_observable';
import {Checks, getAllRouteGuards} from './utils/preactivation';
import {CREATE_VIEW_TRANSITION} from './utils/view_transition';
import {ACTIVATED_ROUTE_INJECTOR_FEATURE} from './activated_route_injector_feature';
import {TreeNode} from './utils/tree';

/**
* @description
Expand Down Expand Up @@ -369,7 +370,7 @@ export class NavigationTransitions {
private readonly urlHandlingStrategy = inject(UrlHandlingStrategy);
private readonly createViewTransition = inject(CREATE_VIEW_TRANSITION, {optional: true});
private readonly navigationErrorHandler = inject(NAVIGATION_ERROR_HANDLER, {optional: true});
private readonly activatedRouteInjectorFeature = inject(ACTIVATED_ROUTE_INJECTOR_FEATURE, {
private readonly routerResourcesFeature = inject(ROUTER_RESOURCES_FEATURE, {
optional: true,
});

Expand Down Expand Up @@ -723,6 +724,7 @@ export class NavigationTransitions {
switchTap((t: NavigationTransition) => {
const loadComponents = (route: ActivatedRouteSnapshot): Array<Promise<void>> => {
const loaders: Array<Promise<void>> = [];

if (route.routeConfig?._loadedComponent) {
route.component = route.routeConfig?._loadedComponent;
} else if (route.routeConfig?.loadComponent) {
Expand Down Expand Up @@ -765,11 +767,9 @@ export class NavigationTransitions {
return of(t);
}),

this.activatedRouteInjectorFeature?.operator() ?? ((t) => t),

this.routerResourcesFeature?.operator(abortController.signal) ?? ((t) => t),
switchTap(() => this.afterPreactivation()),

// TODO(atscott): Move this into the last block below.
switchMap(() => {
const {currentSnapshot, targetSnapshot} = overallTransitionState;
const viewTransitionStarted = this.createViewTransition?.(
Expand Down Expand Up @@ -813,6 +813,12 @@ export class NavigationTransitions {
return;
}

const traverse = (node: TreeNode<ActivatedRoute>) => {
node.value.pending?.set(false);
node.children.forEach(traverse);
};
traverse(t.targetRouterState!._root);

completedOrAborted = true;
this.currentNavigation.update((nav) => {
(nav as Writable<Navigation>).abort = noop;
Expand All @@ -832,7 +838,7 @@ export class NavigationTransitions {

takeUntil(
abortSignalToObservable(abortController.signal).pipe(
// Ignore aborts if we are already completed, canceled, or are in the activation stage (we have targetRouterState)
// Ignore aborts if we are already completed, canceled, or the transition has entered the non-abortable activation stage
filter(() => !completedOrAborted && abortable),
tap(() => {
this.cancelNavigationTransition(
Expand Down Expand Up @@ -893,7 +899,7 @@ export class NavigationTransitions {
}),
catchError((e) => {
completedOrAborted = true;
discardNewActivatedRoutes(overallTransitionState);
rollbackState(overallTransitionState);
// If the application is already destroyed, the catch block should not
// execute anything in practice because other resources have already
// been released and destroyed.
Expand Down Expand Up @@ -996,7 +1002,7 @@ export class NavigationTransitions {
reason: string,
code: NavigationCancellationCode,
) {
discardNewActivatedRoutes(t);
rollbackState(t);
const navCancel = new NavigationCancel(
t.id,
this.urlSerializer.serialize(t.extractedUrl),
Expand Down Expand Up @@ -1050,11 +1056,16 @@ export function isBrowserTriggeredNavigation(source: NavigationTrigger) {
return source !== IMPERATIVE_NAVIGATION;
}

function discardNewActivatedRoutes(t: NavigationTransition): void {
if (!t.newlyCreatedRoutes) {
return;
}
for (const r of t.newlyCreatedRoutes) {
function rollbackState(t: NavigationTransition): void {
for (const r of t.newlyCreatedRoutes ?? []) {
r._localInjector?.destroy();
r._localInjector = undefined;
}
if (t.targetRouterState) {
const traverse = (node: TreeNode<ActivatedRoute>) => {
node.value.pending?.set(false);
node.children.forEach(traverse);
};
traverse(t.targetRouterState._root);
}
}
64 changes: 0 additions & 64 deletions packages/router/src/operators/setup_activated_route_injectors.ts

This file was deleted.

Loading
Loading