Routing
This page provides best practices for routing in Angular, focusing on structure, lazy loading, and navigation.
General guidelines
Consider having a project structure similar to your routes structure, see folder structure.
Consider applying the same naming convention as your API, see API naming convention.
Consider having a client routing as close as possible to your server API routing.
Lazy loading
Consider lazy loading each feature under features
folder.
Do create a sub route file for multi route features.
const routes: Routes = [
{
path: 'admin',
component: AdminPage
},
{
path: 'users',
component: BrowseUsersPage
},
{
path: 'users/:id',
component: UserProfilePage
}
// ...
];
const routes: Routes = [
// use 'loadComponent' to lazy load single route features
{
path: 'admin',
loadComponent: () => import('./features/admin/admin-page').then(c => c.AdminPage)
},
// use 'loadChildren' to lazy load multi route features.
{
path: 'users',
loadChildren: () => import('./features/users/users.routes').then(m => m.USERS_ROUTES)
}
// ...
];
const USERS_ROUTES: Routes = [
{
path: '',
component: BrowseUsersPage
},
{
path: ':id',
component: UserProfilePage
},
// ...
];
Navigation
Do use routerLink
for links over router.navigate()
or router.navigateByUrl()
.
<button (click)="showEmployees()">See employees</button>
<button (click)="showManager(user.id)">See manager</button>
import { Router } from '@angular/router';
export class CompanyPage {
#router = inject(Router);
showEmployees() {
this.#router.navigateByUrl('/users');
}
showManager(managerId: number) {
this.#router.navigate(['users', managerId]);
}
}
<a routerLink="/users">See employees</a>
<a [routerLink]="['/users', user.id]">See manager</a>
RouterLink
uses standard HTML <a>
tags which is better for accessibility, it supports native browser behaviors (opening link in new tab for example).
Only use router
when programmatic navigation is required, such as redirects.
Data fetching
Do use withComponentInputBinding()
for accessing route data (resolver, params and static data).
export class UserPage implements OnInit {
#route = inject(ActivatedRoute);
userId!: string;
user!: User;
ngOnInit(): void {
this.userId = this.route.snapshot.params['id'];
this.user = this.route.snapshot.data['user'];
}
}
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes, withComponentInputBinding()),
// ...
]
};
export class UserPage implements OnInit {
userId = input.required<string>();
user = input.required<User>();
}
Consider fetching data using a resolver instead of inside ngOnInit
lifecycle hook.
export class UserPage implements OnInit {
#userHttpClient = inject(UserHttpClient);
userId = input.required<string>();
// 'user' is undefined until HTTP request is resolved.
user?: User;
ngOnInit(): void {
this.#userHttpClient.getUser(this.userId())
.subscribe(user => this.user = user);
}
}
export class UserPage {
// 'user' will be loaded before the component initializes
// and there is no need to handle the loading state.
user: input.required<User>();
}
const USERS_ROUTES: Routes = [
{
path: ':id',
component: UserPage,
resolve: {
// Define your resolver here
user: (route: ActivatedRouteSnapshot) => {
const userId = route.params['id'];
return inject(UserHttpClient).getUser(userId);
}
}
},
// ...
];
Error handling
Do define a fallback route.
export const routes: Routes = [
...
// Keep this route at the end.
{ path: '**', component: NotFoundPage },
];
Do use withNavigationErrorHandler
to handle navigation errors globally.
export const appConfig: ApplicationConfig = {
providers: [
...,
provideRouter(routes,
withNavigationErrorHandler(error => {
// Fallback to a generic error page
const router = inject(Router);
return new RedirectCommand(router.parseUrl('/error'));
})),
]
};
Do handle resolver errors by returning a RedirectCommand
.
const appRoutes: Routes = [
{
path: 'post/:id',
component: PostPage,
resolve: {
post: postResolver
}
}
];
export const postResolver: ResolveFn<Post> = (route, state) => {
const postHttpClient = inject(PostHttpClient);
const router = inject(Router);
return postHttpClient.getPost(route.params['id']).pipe(
catchError((error: HttpErrorResponse) => {
if (error.status === 404) {
// Redirect to a specific 'Post not found' page
const redirect = new RedirectCommand(router.parseUrl('/post-not-found'));
return of(redirect);
} else {
// Throw unhandled error further, will be caught by the global navigation error handler
return throwError(() => error);
}
})
);
};