Routing

Angular · Practical · 15 min

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 /> (import RouterOutlet), where the matched component renders.
  • Lazy loading with loadComponent (one component) or loadChildren (a child route array) splits the bundle so code loads on demand.
  • children define nested routes rendered in a nested outlet; redirectTo with pathMatch: 'full' redirects; '**' catches everything else (put it last; routes match in order).
  • title sets 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.

Check yourself

  1. Navigating from /products/1 to /products/2 shows the same component. With withComponentInputBinding and id = input.required<string>(), what happens?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: angular.dev/guide/routing