From 00b10cd589d8ee270888ae5d5802090516c5265f Mon Sep 17 00:00:00 2001 From: diegoinost02 Date: Fri, 16 Oct 2026 13:04:15 -0300 Subject: [PATCH] docs: add router resources reference to developer skill --- skills/dev-skills/angular-developer/SKILL.md | 2 +- .../references/data-resolvers.md | 97 +++++++++++++++++++ 2 files changed, 98 insertions(+), 1 deletion(-) diff --git a/skills/dev-skills/angular-developer/SKILL.md b/skills/dev-skills/angular-developer/SKILL.md index d6696a50f821..85a7cc537a8c 100644 --- a/skills/dev-skills/angular-developer/SKILL.md +++ b/skills/dev-skills/angular-developer/SKILL.md @@ -112,7 +112,7 @@ When implementing navigation in Angular, consult the following references: - **Show Routes with Outlets**: Using ``, nested outlets, and named outlets. Read [show-routes-with-outlets.md](references/show-routes-with-outlets.md) - **Navigate to Routes**: Declarative navigation with `RouterLink` and programmatic navigation with `Router`. Read [navigate-to-routes.md](references/navigate-to-routes.md) - **Control Route Access with Guards**: Implementing `CanActivate`, `CanMatch`, and other guards for security. Read [route-guards.md](references/route-guards.md) -- **Data Resolvers**: Pre-fetching data before route activation with `ResolveFn`. Read [data-resolvers.md](references/data-resolvers.md) +- **Data Resolvers**: Pre-fetching data before route activation with `ResolveFn`, and router resources (`withRouterResources`) as a reactive, `resource()`-based alternative. Read [data-resolvers.md](references/data-resolvers.md) - **Router Lifecycle and Events**: Chronological order of navigation events and debugging. Read [router-lifecycle.md](references/router-lifecycle.md) - **Rendering Strategies**: Choosing between CSR, SSG (prerendering), and SSR. Read [rendering-strategies.md](references/rendering-strategies.md) - **Server and Hybrid Rendering**: Implementing server routes, prerendering, hydration, server-compatible code, and request data in an SSR-configured app. Read [server-side-rendering.md](references/server-side-rendering.md) diff --git a/skills/dev-skills/angular-developer/references/data-resolvers.md b/skills/dev-skills/angular-developer/references/data-resolvers.md index abff11f8fd2c..8fa6dbe7912b 100644 --- a/skills/dev-skills/angular-developer/references/data-resolvers.md +++ b/skills/dev-skills/angular-developer/references/data-resolvers.md @@ -63,6 +63,103 @@ return userService .pipe(catchError(() => of(new RedirectCommand(router.parseUrl('/error'))))); ``` +## Router Resources: A `resource()`-Based Alternative + +IMPORTANT: `withRouterResources`, `ResourceContext`, and `nonBlocking` require Angular v22.2 or +newer. + +Where `resolve` gives a one-shot value, a route's `resources` property gives a map of +signal-backed `resource()`s, keyed by name, each independently re-fetchable as route state changes. +Enable `withComponentInputBinding()` alongside `withRouterResources()` so the router can bind +resolved resources directly to component inputs: + +```ts +import { provideRouter, withComponentInputBinding, withRouterResources } from '@angular/router'; + +// app.config.ts +providers: [provideRouter(routes, withComponentInputBinding(), withRouterResources())]; +``` + +Define resources inline as a function on the route. It runs in an injection context, so `inject()` +works directly inside it: + +```ts +import { inject, resource } from '@angular/core'; + +{ + path: 'user/:id', + component: UserProfile, + resources: (ctx) => { + const userService = inject(UserService); + return { + user: resource({ + params: () => ctx.params()['id'], + loader: ({ params: id }) => userService.getUser(id), + }), + }; + }, +} +``` + +By default a resource is **blocking**: the router waits for it to resolve before activating the +route, then binds only the resolved value to the matching component input: + +```ts +export class UserProfile { + // The router binds only the value for blocking resources. + user = input.required(); +} +``` + +Wrap a resource in `nonBlocking()` to activate the route immediately instead of waiting for it. +The router then binds the full `Resource` object to the input, giving access to `isLoading()`, +`error()`, and `value()`: + +```ts +import { nonBlocking } from '@angular/router'; + +resources: () => ({ + reportData: nonBlocking(resource({ loader: () => fetchHeavyReportData() })), +}), +``` + +```ts +export class Reports { + reportData = input.required>(); +} +``` + +```html +@if (reportData().isLoading()) { +

Loading...

+} @else if (reportData().error()) { +

Error loading report.

+} @else if (reportData().hasValue()) { + +} +``` + +- `ResourceContext` exposes `params`, `queryParams`, `fragment`, and `data` as signals, not + snapshots — a resource re-fetches automatically when a signal it reads changes, so navigating + between routes that reuse the same component re-runs the resource instead of requiring a manual + refresh. +- If a blocking resource errors, the router cancels the navigation and emits a `NavigationError`; + a `nonBlocking()` resource instead completes the navigation and surfaces the failure through its + own `error()` signal. +- The component input only carries the resolved value for a blocking resource, not the `Resource` + instance — read it from `ActivatedRoute` when you need to call `.reload()`: + +```ts +export class UserProfile { + user = input.required(); + private userResource = inject(ActivatedRoute).resources?.['user']; + + refreshUser() { + this.userResource?.reload(); + } +} +``` + ## Best Practices - **Keep it lightweight**: Fetch only critical data.