Every multi-page Angular app is built around the router.
The Angular router maps URLs to components. Define a Routes array and register it with provideRouter(routes) in app.config.ts:
export const routes: Routes = [
{ path: '', component: Home, title: 'Home' },
{ path: 'products/:id', loadComponent: () => import('./product/product').then((m) => m.ProductPage) },
{ path: '**', component: NotFound },
];- The root template contains
<router-outlet />(importRouterOutlet), where the matched component renders. - Lazy loading with
loadComponent(one component) orloadChildren(a child route array) splits the bundle so code loads on demand. childrendefine nested routes rendered in a nested outlet;redirectTowithpathMatch: 'full'redirects;'**'catches everything else (put it last; routes match in order).titlesets the document title.
Navigation: <a routerLink="/products/42"> or [routerLink]="['/products', p.id]" (import RouterLink), with routerLinkActive="active" for styling. In code, inject(Router).navigate(['/products', id]).
Reading parameters: with provideRouter(routes, withComponentInputBinding()), route params, query params and resolved data are bound to component inputs of the same name: id = input.required<string>(). Because it is a signal, the component updates when you navigate from /products/1 to /products/2 (the same component instance is reused). Without input binding, inject ActivatedRoute and read its observables.
Example
// app.routes.ts
import { Routes } from '@angular/router';
export const routes: Routes = [
{ path: '', loadComponent: () => import('./home').then((m) => m.Home), title: 'Shop' },
{ path: 'products/:id', loadComponent: () => import('./product-page').then((m) => m.ProductPage) },
{ path: 'old-shop', redirectTo: '', pathMatch: 'full' },
{ path: '**', loadComponent: () => import('./not-found').then((m) => m.NotFound) },
];
// app.config.ts: providers: [provideRouter(routes, withComponentInputBinding())]
// product-page.ts
import { Component, computed, input } from '@angular/core';
import { RouterLink } from '@angular/router';
@Component({
selector: 'app-product-page',
imports: [RouterLink],
template: `<h1>Product {{ id() }}</h1>
<a [routerLink]="['/products', nextId()]">Next product</a>
<a routerLink="/">Back to shop</a>`,
})
export class ProductPage {
id = input.required<string>(); // from :id via input binding
protected nextId = computed(() => Number(this.id()) + 1);
}Remember
- Define
Routes,provideRouter(routes), render in<router-outlet /> - Lazy-load with
loadComponent/loadChildren withComponentInputBinding()maps params to inputs; the component is reused across param changes
Watch out for
- Placing the
'**'wildcard route before other routes; matching is first-come, so it swallows everything.