Angular Routing
The Angular Router maps URLs to components, turning an Angular app into a multi-page experience without full page reloads. It handles route parameters and query strings, nested layouts, lazy loading of feature areas, navigation guards for authentication and unsaved changes, data prefetching with resolvers, page titles, scroll restoration, and view transitions.
Modern Angular routing is standalone and functional: routes are configured with provideRouter(), components are lazy-loaded with loadComponent, guards and resolvers are plain functions using inject(), and route parameters can bind directly to component inputs. Well-designed routes keep initial bundles small and navigation fast.
TL;DR
- Configure routes with
provideRouter(routes, …features)and render them in<router-outlet />. - Lazy load with
loadComponent(one component) orloadChildren(a route array). withComponentInputBinding()binds route params, query params, and resolved data to component inputs.- Functional guards:
canActivate,canMatch,canDeactivate, andcanActivateChildreturn a boolean, aUrlTree, or an observable or promise of those. - Resolvers prefetch data before activation (use sparingly; loading states are often better).
- Use
routerLink,Router.navigate, preloading strategies, route titles, and a wildcard 404 route.
Quick Example
Core Concepts
Route Configuration
Each route has a path (with :param segments), and either component/loadComponent, children, loadChildren, or redirectTo. Options include pathMatch: 'full' (for empty-path redirects), title (a string or a resolver), data, providers (route-scoped DI; see DI), guards, and resolvers. Order matters: the router uses the first match, so put specific paths before general ones and the ** wildcard last.
Lazy Loading
loadComponentlazy-loads a single standalone component.loadChildrenlazy-loads a route array (a feature area), often with its own providers.
Lazy chunks keep the initial bundle small. Preloading strategies (PreloadAllModules, or custom strategies based on route data or network conditions) fetch lazy chunks in the background after the app loads, so later navigations are instant.
Route Parameters and Component Input Binding
With withComponentInputBinding(), path params (:id), query params, route data, and resolved data are passed to component inputs with matching names, so components don't need to inject ActivatedRoute. Otherwise, read ActivatedRoute.paramMap or queryParamMap (observables), converting with toSignal if desired. Components are reused when only params change, so treat inputs reactively.
Guards
Guards are functions using inject(). Return true/false or a UrlTree (redirect), or a RedirectCommand for more control. Guards are UX and routing controls, not security: the server must still authorize every API call. See broken access control.
Resolvers
A resolver (ResolveFn) fetches data before the route activates, making the data available via route.data or inputs. It's useful when a page is meaningless without its data, but it delays navigation with no visible feedback. Often a better UX is navigating immediately and showing loading states with resource()/httpResource() (see signals) or @defer.
Navigation
- Templates:
<a routerLink="/products" [queryParams]="{ page: 2 }" routerLinkActive="active">. - Code:
router.navigate(['/products', id], { queryParams: { tab: 'reviews' } })orrouter.navigateByUrl('/account'). - Router events (
NavigationStart,NavigationEnd,NavigationError) power progress bars and analytics.
Layouts With Child Routes
Parent routes with children render a layout component containing its own <router-outlet />: a shared shell (sidebar, tabs) whose content changes by child route. Named outlets support secondary views like side panels.
SSR and Hydration
With Angular SSR, routes render on the server for fast first paint and SEO, then hydrate on the client. Route-level render modes (server, client, or prerender) allow static prerendering of marketing pages while keeping dashboards client-rendered. Guards and resolvers run on the server too, so avoid browser-only APIs in them without platform checks.
Best Practices
Lazy Load Every Feature Area
Keep the root routes thin and lazy-load features with loadChildren or loadComponent. Combine with preloading for fast subsequent navigation, and with @defer within pages.
Use canMatch for Role-Gated Features
canMatch prevents downloading admin code for users who can't access it, and allows the same path to map to different components by role or feature flag.
Bind Params to Inputs
withComponentInputBinding() plus signal inputs keeps components decoupled from the router, and easy to test by setting inputs directly.
Always Have a Not-Found Route
A ** route with a helpful 404 page, and correct server status codes in SSR, handles broken links gracefully.
Common Mistakes
Wrong Route Order
Place static segments before parameterized ones.
Empty-Path Redirect Without pathMatch: 'full'
{ path: '', redirectTo: 'home' } with the default prefix matching matches every URL, causing redirect loops or unexpected behavior. Use pathMatch: 'full' for empty-path redirects.
Relying on Guards for Security
Hiding routes client-side doesn't protect data. Users can call APIs directly, or modify client code. Enforce authorization on the server.
FAQ
How do I lazy load routes in Angular?
Use loadComponent: () => import('./x.component').then(m => m.XComponent) for a single standalone component, or loadChildren: () => import('./feature.routes') for a set of routes. The Angular CLI splits each into its own chunk, loaded on first navigation, or earlier with preloading.
What's the difference between canActivate and canMatch?
canActivate runs after the route has matched (and lazy code may have been loaded) and decides whether to activate it. canMatch runs during matching. If it fails, the router tries other routes, and the lazy chunk isn't downloaded. Use canMatch for role- or flag-based route variants.
How do I get route parameters in a component?
Enable withComponentInputBinding() and declare inputs named after the parameters (id = input<string>()), or inject ActivatedRoute and read paramMap or snapshot.paramMap. Remember that components are reused when only params change, so react to changes rather than reading once.
Should I use resolvers?
Use them when a page truly can't render meaningfully without the data, and it loads quickly. Otherwise, navigate immediately and show loading states, which feels faster to users. Resolvers delay navigation without feedback unless you add a global progress indicator.
Related Topics
- Angular — The framework overview
- Angular Components — Route components and @defer
- Angular Dependency Injection — Route providers and inject() in guards
- Angular Signals — Reactive data loading per route
- View Transitions — Animated navigations
- SEO for SPAs — Server rendering and crawlability