Skip to content

Commit 2e468c9

Browse files
authored
feat(docs): translate data resolvers documentation to Japanese (#1058)
1 parent 8ac44ba commit 2e468c9

File tree

2 files changed

+351
-53
lines changed

2 files changed

+351
-53
lines changed
Lines changed: 298 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,298 @@
1+
# Data resolvers
2+
3+
Data resolvers allow you to fetch data before navigating to a route, ensuring that your components receive the data they need before rendering. This can help prevent the need for loading states and improve the user experience by pre-loading essential data.
4+
5+
## What are data resolvers?
6+
7+
A data resolver is a service that implements the [`ResolveFn`](api/router/ResolveFn) function. It runs before a route activates and can fetch data from APIs, databases, or other sources. The resolved data becomes available to the component through the [`ActivatedRoute`](api/router/ActivatedRoute).
8+
9+
## Why use data resolvers?
10+
11+
Data resolvers solve common routing challenges:
12+
13+
- **Prevent empty states**: Components receive data immediately upon loading
14+
- **Better user experience**: No loading spinners for critical data
15+
- **Error handling**: Handle data fetching errors before navigation
16+
- **Data consistency**: Ensure required data is available before rendering which is important for SSR
17+
18+
## Creating a resolver
19+
20+
You create a resolver by writing a function with the [`ResolveFn`](api/router/ResolveFn) type.
21+
22+
It receives the [`ActivatedRouteSnapshot`](api/router/ActivatedRouteSnapshot) and [`RouterStateSnapshot`](api/router/RouterStateSnapshot) as parameters.
23+
24+
Here is a resolver that gets the user information before rendering a route using the [`inject`](api/core/inject) function:
25+
26+
```ts
27+
import { inject } from '@angular/core';
28+
import { UserStore, SettingsStore } from './user-store';
29+
import type { ActivatedRouteSnapshot, ResolveFn, RouterStateSnapshot } from '@angular/router';
30+
import type { User, Settings } from './types';
31+
32+
export const userResolver: ResolveFn<User> = (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => {
33+
const userStore = inject(UserStore);
34+
const userId = route.paramMap.get('id')!;
35+
return userStore.getUser(userId);
36+
};
37+
38+
export const settingsResolver: ResolveFn<Settings> = (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => {
39+
const settingsStore = inject(SettingsStore);
40+
const userId = route.paramMap.get('id')!;
41+
return settingsStore.getUserSettings(userId);
42+
};
43+
```
44+
45+
## Configuring routes with resolvers
46+
47+
When you want to add one or more data resolvers to a route, you can add it under the `resolve` key in the route configuration. The [`Routes`](api/router/Routes) type defines the structure for route configurations:
48+
49+
```ts
50+
import { Routes } from '@angular/router';
51+
52+
export const routes: Routes = [
53+
{
54+
path: 'user/:id',
55+
component: UserDetail,
56+
resolve: {
57+
user: userResolver,
58+
settings: settingsResolver
59+
}
60+
}
61+
];
62+
```
63+
64+
You can learn more about the [`resolve` configuration in the API docs](api/router/Route#resolve).
65+
66+
## Accessing resolved data in components
67+
68+
### Using ActivatedRoute
69+
70+
You can access the resolved data in a component by accessing the snapshot data from the [`ActivatedRoute`](api/router/ActivatedRoute) using the [`signal`](api/core/signal) function:
71+
72+
```angular-ts
73+
import { Component, inject, computed } from '@angular/core';
74+
import { ActivatedRoute } from '@angular/router';
75+
import { toSignal } from '@angular/core/rxjs-interop';
76+
import type { User, Settings } from './types';
77+
78+
@Component({
79+
template: `
80+
<h1>{{ user().name }}</h1>
81+
<p>{{ user().email }}</p>
82+
<div>Theme: {{ settings().theme }}</div>
83+
`
84+
})
85+
export class UserDetail {
86+
private route = inject(ActivatedRoute);
87+
private data = toSignal(this.route.data);
88+
user = computed(() => this.data().user as User);
89+
settings = computed(() => this.data().settings as Settings);
90+
}
91+
```
92+
93+
### Using withComponentInputBinding
94+
95+
A different approach to accessing the resolved data is to use [`withComponentInputBinding()`](api/router/withComponentInputBinding) when configuring your router with [`provideRouter`](api/router/provideRouter). This allows resolved data to be passed directly as component inputs:
96+
97+
```ts
98+
import { bootstrapApplication } from '@angular/platform-browser';
99+
import { provideRouter, withComponentInputBinding } from '@angular/router';
100+
import { routes } from './app.routes';
101+
102+
bootstrapApplication(App, {
103+
providers: [
104+
provideRouter(routes, withComponentInputBinding())
105+
]
106+
});
107+
```
108+
109+
With this configuration, you can define inputs in your component that match the resolver keys using the [`input`](api/core/input) function and [`input.required`](api/core/input#required) for required inputs:
110+
111+
```angular-ts
112+
import { Component, input } from '@angular/core';
113+
import type { User, Settings } from './types';
114+
115+
@Component({
116+
template: `
117+
<h1>{{ user().name }}</h1>
118+
<p>{{ user().email }}</p>
119+
<div>Theme: {{ settings().theme }}</div>
120+
`
121+
})
122+
export class UserDetail {
123+
user = input.required<User>();
124+
settings = input.required<Settings>();
125+
}
126+
```
127+
128+
This approach provides better type safety and eliminates the need to inject `ActivatedRoute` just to access resolved data.
129+
130+
## Error handling in resolvers
131+
132+
In the event of navigation failures, it is important to handle errors gracefully in your data resolvers. Otherwise, a `NavigationError` will occur and the navigation to the current route will fail which will lead to a poor experience for your users.
133+
134+
There are three primary ways to handle errors with data resolvers:
135+
136+
1. [Centralizing error handling in `withNavigationErrorHandler`](#centralize-error-handling-in-withnavigationerrorhandler)
137+
2. [Managing errors through a subscription to router events](#managing-errors-through-a-subscription-to-router-events)
138+
3. [Handling errors directly in the resolver](#handling-errors-directly-in-the-resolver)
139+
140+
### Centralize error handling in `withNavigationErrorHandler`
141+
142+
The [`withNavigationErrorHandler`](api/router/withNavigationErrorHandler) feature provides a centralized way to handle all navigation errors, including those from failed data resolvers. This approach keeps error handling logic in one place and prevents duplicate error handling code across resolvers.
143+
144+
```ts
145+
import { bootstrapApplication } from '@angular/platform-browser';
146+
import { provideRouter, withNavigationErrorHandler } from '@angular/router';
147+
import { inject } from '@angular/core';
148+
import { Router } from '@angular/router';
149+
import { routes } from './app.routes';
150+
151+
bootstrapApplication(App, {
152+
providers: [
153+
provideRouter(routes, withNavigationErrorHandler((error) => {
154+
const router = inject(Router);
155+
156+
if (error?.message) {
157+
console.error('Navigation error occurred:', error.message)
158+
}
159+
160+
router.navigate(['/error']);
161+
}))
162+
]
163+
});
164+
```
165+
166+
With this configuration, your resolvers can focus on data fetching while letting the centralized handler manage error scenarios:
167+
168+
```ts
169+
export const userResolver: ResolveFn<User> = (route) => {
170+
const userStore = inject(UserStore);
171+
const userId = route.paramMap.get('id')!;
172+
// No need for explicit error handling - let it bubble up
173+
return userStore.getUser(userId);
174+
};
175+
```
176+
177+
### Managing errors through a subscription to router events
178+
179+
You can also handle resolver errors by subscribing to router events and listening for [`NavigationError`](api/router/NavigationError) events. This approach gives you more granular control over error handling and allows you to implement custom error recovery logic.
180+
181+
```angular-ts
182+
import { Component, inject, signal } from '@angular/core';
183+
import { Router, NavigationError } from '@angular/router';
184+
import { toSignal } from '@angular/core/rxjs-interop';
185+
import { filter, map } from 'rxjs';
186+
187+
@Component({
188+
selector: 'app-root',
189+
template: `
190+
@if (errorMessage()) {
191+
<div class="error-banner">
192+
{{ errorMessage() }}
193+
<button (click)="retryNavigation()">Retry</button>
194+
</div>
195+
}
196+
<router-outlet />
197+
`
198+
})
199+
export class App {
200+
private router = inject(Router);
201+
private lastFailedUrl = signal('');
202+
203+
private navigationErrors = toSignal(
204+
this.router.events.pipe(
205+
filter((event): event is NavigationError => event instanceof NavigationError),
206+
map(event => {
207+
this.lastFailedUrl.set(event.url);
208+
209+
if (event.error) {
210+
console.error('Navigation error', event.error)
211+
}
212+
213+
return 'Navigation failed. Please try again.';
214+
})
215+
),
216+
{ initialValue: '' }
217+
);
218+
219+
errorMessage = this.navigationErrors;
220+
221+
retryNavigation() {
222+
if (this.lastFailedUrl()) {
223+
this.router.navigateByUrl(this.lastFailedUrl());
224+
}
225+
}
226+
}
227+
```
228+
229+
This approach is particularly useful when you need to:
230+
231+
- Implement custom retry logic for failed navigation
232+
- Show specific error messages based on the type of failure
233+
- Track navigation failures for analytics purposes
234+
235+
### Handling errors directly in the resolver
236+
237+
Here's an updated example of the `userResolver` that logs the error and navigates back to the generic `/users` page using the [`Router`](api/router/Router) service:
238+
239+
```ts
240+
import { inject } from '@angular/core';
241+
import { ResolveFn, RedirectCommand, Router } from '@angular/router';
242+
import { catchError, of, EMPTY } from 'rxjs';
243+
import { UserStore } from './user-store';
244+
import type { User } from './types';
245+
246+
export const userResolver: ResolveFn<User | RedirectCommand> = (route) => {
247+
const userStore = inject(UserStore);
248+
const router = inject(Router);
249+
const userId = route.paramMap.get('id')!;
250+
251+
return userStore.getUser(userId).pipe(
252+
catchError(error => {
253+
console.error('Failed to load user:', error);
254+
return of(new RedirectCommand(router.parseUrl('/users')));
255+
})
256+
);
257+
};
258+
```
259+
260+
## Navigation loading considerations
261+
262+
While data resolvers prevent loading states within components, they introduce a different UX consideration: navigation is blocked while resolvers execute. Users may experience delays between clicking a link and seeing the new route, especially with slow network requests.
263+
264+
### Providing navigation feedback
265+
266+
To improve user experience during resolver execution, you can listen to router events and show loading indicators:
267+
268+
```angular-ts
269+
import { Component, inject } from '@angular/core';
270+
import { Router } from '@angular/router';
271+
import { toSignal } from '@angular/core/rxjs-interop';
272+
import { map } from 'rxjs';
273+
274+
@Component({
275+
selector: 'app-root',
276+
template: `
277+
@if (isNavigating()) {
278+
<div class="loading-bar">Loading...</div>
279+
}
280+
<router-outlet />
281+
`
282+
})
283+
export class App {
284+
private router = inject(Router);
285+
isNavigating = computed(() => !!this.router.currentNavigation());
286+
}
287+
```
288+
289+
This approach ensures users receive visual feedback that navigation is in progress while resolvers fetch data.
290+
291+
## Best practices
292+
293+
- **Keep resolvers lightweight**: Resolvers should fetch essential data only and not everything the page could possibly need
294+
- **Handle errors**: Always remember to handle errors gracefully to provide the best experience possible to users
295+
- **Use caching**: Consider caching resolved data to improve performance
296+
- **Consider navigation UX**: Implement loading indicators for resolver execution since navigation is blocked during data fetching
297+
- **Set reasonable timeouts**: Avoid resolvers that could hang indefinitely and block navigation
298+
- **Type safety**: Use TypeScript interfaces for resolved data

0 commit comments

Comments
 (0)