Angular Router

The Angular Router turns a single-page application into a multi-view experience by mapping URL paths to components, all without a full page reload.

Setting Up the Angular Router

In a modern standalone Angular application, routing is configured with provideRouter rather than importing RouterModule.forRoot into an NgModule. You define an array of Route objects — each mapping a path string to a component — and pass that array to provideRouter in your application's bootstrap configuration. The router then renders the matched component wherever you place a <router-outlet> in your template.

Defining Routes

A Route is a plain object with a path and a component (or, for lazy loading, a loadComponent function returning a dynamic import). Paths are matched in order, so more specific routes must come before general ones, and a wildcard route using '**' should always be listed last to catch anything unmatched.

Configuring routes for a standalone app

import { Component } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { provideRouter, RouterOutlet, Routes } from '@angular/router';

@Component({
  selector: 'app-home',
  standalone: true,
  template: `
    <h2>Home</h2>
    <p>Welcome to the home page.</p>
  `
})
export class HomeComponent {}

@Component({
  selector: 'app-about',
  standalone: true,
  template: `
    <h2>About</h2>
    <p>This is the about page.</p>
  `
})
export class AboutComponent {}

@Component({
  selector: 'app-not-found',
  standalone: true,
  template: `
    <h2>404</h2>
    <p>Page not found.</p>
  `
})
export class NotFoundComponent {}

export const routes: Routes = [
  { path: '', component: HomeComponent },
  { path: 'about', component: AboutComponent },
  { path: '**', component: NotFoundComponent }
];

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [RouterOutlet],
  template: `<router-outlet></router-outlet>`
})
export class AppComponent {}

bootstrapApplication(AppComponent, {
  providers: [provideRouter(routes)]
}).catch((err) => console.error(err));

Navigating with routerLink

Instead of plain <a href> tags, use the routerLink directive so navigation goes through the Angular Router without triggering a full page reload. Pair it with routerLinkActive to automatically apply a CSS class to the link matching the current URL, which is exactly what you want for highlighting the active tab in a navigation bar. For navigation triggered from code — after a form submits, for example — inject the Router service and call its navigate method.

A navigation bar with routerLink and router-outlet

import { Component } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { provideRouter, RouterLink, RouterLinkActive, RouterOutlet, Routes } from '@angular/router';

@Component({
  selector: 'app-home',
  standalone: true,
  template: `<p>Home page content.</p>`
})
export class HomeComponent {}

@Component({
  selector: 'app-about',
  standalone: true,
  template: `<p>About page content.</p>`
})
export class AboutComponent {}

const routes: Routes = [
  { path: '', component: HomeComponent },
  { path: 'about', component: AboutComponent }
];

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [RouterLink, RouterLinkActive, RouterOutlet],
  template: `
    <nav>
      <a routerLink="/" routerLinkActive="active" [routerLinkActiveOptions]="{ exact: true }">
        Home
      </a>
      <a routerLink="/about" routerLinkActive="active">About</a>
    </nav>

    <router-outlet></router-outlet>
  `
})
export class AppComponent {}

bootstrapApplication(AppComponent, {
  providers: [provideRouter(routes)]
}).catch((err) => console.error(err));
  • Route parameters — dynamic segments like :id captured from the URL
  • Query parameters — optional key/value pairs appended after ?
  • Route guards — functions that can allow, block, or redirect navigation
  • Lazy loading — loadComponent or loadChildren to split code by route
  • Nested routes — child <router-outlet> elements for layouts within layouts
ConceptPurposeExample
provideRouter(routes)Registers the router in a standalone appbootstrapApplication(AppComponent, { providers: [provideRouter(routes)] })
router-outletMarks where the matched component renders<router-outlet></router-outlet>
routerLinkDeclarative navigation in templates<a routerLink="/about">About</a>
Router.navigate()Programmatic navigation from codethis.router.navigate(['/products', id])

Reading route parameters and navigating back

import { Component, inject } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { ActivatedRoute, provideRouter, Router, RouterOutlet, Routes } from '@angular/router';
import { toSignal } from '@angular/core/rxjs-interop';
import { map } from 'rxjs';

@Component({
  selector: 'app-product-detail',
  standalone: true,
  template: `
    <h2>Product #{{ productId() }}</h2>
    <button (click)="goBack()">Back to list</button>
  `
})
export class ProductDetailComponent {
  private route = inject(ActivatedRoute);
  private router = inject(Router);

  productId = toSignal(
    this.route.paramMap.pipe(map(params => params.get('id'))),
    { initialValue: null }
  );

  goBack(): void {
    this.router.navigate(['/products']);
  }
}

@Component({
  selector: 'app-product-list',
  standalone: true,
  template: `<p>Product list placeholder. Try navigating to /products/42.</p>`
})
export class ProductListComponent {}

export const routes: Routes = [
  { path: 'products', component: ProductListComponent },
  { path: 'products/:id', component: ProductDetailComponent },
  { path: '', redirectTo: 'products/42', pathMatch: 'full' }
];

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [RouterOutlet],
  template: `<router-outlet></router-outlet>`
})
export class AppComponent {}

bootstrapApplication(AppComponent, {
  providers: [provideRouter(routes)]
}).catch((err) => console.error(err));
Note: You'll still see RouterModule.forRoot(routes) and RouterModule.forChild(routes) in NgModule-based codebases — they configure the exact same router under the hood. provideRouter is simply the standalone-component equivalent, and it's the recommended approach for new Angular applications.
Note: Route order matters because the router matches top to bottom and stops at the first match. A wildcard route ('**') placed before your real routes will swallow every navigation, so it must always be the last entry in the array.

Exercise: Angular Router

In a standalone app, how are routes typically registered?