On this page
Advanced routing: guards, resolvers, and layouts
Angular's routing system
Angular's router maps URLs to components. In Angular 21, routes are defined as an array of Route objects in a dedicated file.
Configuring the router
In app.config.ts, provide the router with the necessary options:
import { ApplicationConfig } from '@angular/core';
import { provideRouter, withComponentInputBinding } from '@angular/router';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(
routes,
withComponentInputBinding(), // Parameters as inputs
),
],
};Lazy loading
Lazy loading loads components only when the user navigates to their route:
// A single component
{
path: 'about',
loadComponent: () => import('./about').then(m => m.About),
}
// Route group (separate file)
{
path: 'admin',
loadChildren: () => import('./admin/routes').then(m => m.adminRoutes),
}Route parameters
With withComponentInputBinding(), route parameters are injected as inputs:
// Route: /courses/:slug
@Component({ /* ... */ })
export class CourseDetail {
// The router injects the :slug parameter value
readonly slug = input.required<string>();
}You can also access query params:
// URL: /search?q=angular
@Component({ /* ... */ })
export class Search {
readonly q = input<string>(''); // query param "q"
}Functional guards
Guards protect routes. In Angular 21, they are implemented as simple functions:
function authGuard() {
const auth = inject(AuthService);
const router = inject(Router);
if (auth.isAuthenticated()) return true;
return router.createUrlTree(['/login']);
}
// Usage in the route
{ path: 'dashboard', canActivate: [authGuard], /* ... */ }Available guard types:
| Guard | When it runs |
|---|---|
canActivate |
Before activating the route |
canDeactivate |
Before leaving the route |
canMatch |
Before attempting to match the route |
resolve |
Before rendering, to pre-load data |
Resolvers
Resolvers load data before the component renders:
function courseResolver(route: ActivatedRouteSnapshot) {
const courses = inject(CoursesService);
const slug = route.paramMap.get('slug')!;
return courses.getBySlug(slug);
}
// In the route
{
path: ':slug',
loadComponent: () => import('./detail').then(m => m.Detail),
resolve: { course: courseResolver },
}The resolved data is injected as an input in the component:
@Component({ /* ... */ })
export class Detail {
readonly course = input.required<Course>(); // Injected by the resolver
}Nested layouts
Use <router-outlet> inside a layout component to create hierarchies:
// dashboard-layout.ts
@Component({
selector: 'app-dashboard-layout',
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [RouterOutlet, RouterLink, RouterLinkActive],
template: `
<aside><nav>...</nav></aside>
<main><router-outlet /></main>
`,
})
export class DashboardLayout {}routerLinkActive
Adds a CSS class when the link matches the active route:
<a routerLink="/courses" routerLinkActive="active">Courses</a>Practice
- Set up lazy loading: Create two routes with
loadComponentthat lazily load components. Verify in DevTools (Network) that chunks load only when navigating. - Implement a functional guard: Create an
authGuardthat checks if anisAuthenticatedsignal istrue. If not, redirect to/loginusingrouter.createUrlTree(). - Build a nested layout: Implement a layout component with a sidebar and
<router-outlet>. Define child routes that render inside the layout and userouterLinkActiveto highlight the active link.
In the next lesson, we will learn about reactive forms for handling user input with validation.
import { ActivatedRouteSnapshot, Router, Routes } from '@angular/router';
import { inject } from '@angular/core';
import { AuthService } from './auth.service';
// --- Functional guard ---
function authGuard() {
const auth = inject(AuthService);
const router = inject(Router);
if (auth.isAuthenticated()) return true;
return router.createUrlTree(['/login']);
}
function adminGuard() {
const auth = inject(AuthService);
return auth.isAdmin();
}
// --- Functional resolver ---
function courseResolver(route: ActivatedRouteSnapshot) {
const courses = inject(CoursesService);
return courses.getBySlug(route.paramMap.get('slug')!);
}
// --- App routes ---
export const routes: Routes = [
{
path: '',
loadComponent: () => import('./home').then(m => m.Home),
},
{
path: 'courses',
children: [
{
path: '',
loadComponent: () =>
import('./catalog').then(m => m.Catalog),
},
{
path: ':slug',
loadComponent: () =>
import('./course-detail').then(m => m.CourseDetail),
resolve: { course: courseResolver },
},
],
},
{
path: 'dashboard',
canActivate: [authGuard],
loadComponent: () =>
import('./dashboard/layout').then(m => m.DashboardLayout),
children: [
{ path: '', redirectTo: 'overview', pathMatch: 'full' },
{
path: 'overview',
loadComponent: () =>
import('./dashboard/overview').then(m => m.Overview),
},
{
path: 'progress',
loadComponent: () =>
import('./dashboard/progress').then(m => m.Progress),
},
],
},
{
path: 'admin',
canActivate: [authGuard, adminGuard],
loadChildren: () =>
import('./admin/admin.routes').then(m => m.adminRoutes),
},
{ path: '**', redirectTo: '' },
];
Sign in to track your progress