Angular Routing and Navigation
Angular's Router maps URL paths to components, enabling single-page application (SPA) navigation. The URL changes, the component swaps — but the page never fully reloads. You get browser history, deep-linking, and bookmarks for free.
This page covers route configuration, navigation, active link styling, and route redirection.
Setting Up the Router
// main.ts — standalone bootstrap
import { bootstrapApplication } from '@angular/platform-browser';
import { provideRouter } from '@angular/router';
import { AppComponent } from './app/app.component';
import { routes } from './app/app.routes';
bootstrapApplication(AppComponent, {
providers: [
provideRouter(routes),
],
});// app.routes.ts
import { Routes } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { AboutComponent } from './about/about.component';
export const routes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'about', component: AboutComponent },
{ path: '**', redirectTo: '' }, // wildcard — catch-all
];// app.component.ts — add RouterOutlet to the root component
import { Component } from '@angular/core';
import { RouterOutlet, RouterLink, RouterLinkActive } from '@angular/router';
@Component({
standalone: true,
selector: 'app-root',
imports: [RouterOutlet, RouterLink, RouterLinkActive],
template: `
<nav>
<a routerLink="/" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}">Home</a>
<a routerLink="/about" routerLinkActive="active">About</a>
</nav>
<router-outlet />
`,
})
export class AppComponent {}<router-outlet /> is the placeholder where Angular renders the matched component. You can have multiple named outlets in one template — useful for sidebars or modals.Route Configuration Reference
import { Routes } from '@angular/router';
export const routes: Routes = [
// Static route
{ path: 'home', component: HomeComponent },
// Dynamic segment
{ path: 'users/:id', component: UserDetailComponent },
// Redirect
{ path: '', redirectTo: '/home', pathMatch: 'full' },
// Lazy-loaded standalone component
{
path: 'dashboard',
loadComponent: () =>
import('./dashboard/dashboard.component').then(m => m.DashboardComponent),
},
// Lazy-loaded feature routes
{
path: 'products',
loadChildren: () =>
import('./products/products.routes').then(m => m.PRODUCT_ROUTES),
},
// Route with data (static metadata)
{
path: 'settings',
component: SettingsComponent,
data: { title: 'Settings', requiresAuth: true },
},
// Wildcard — must be last
{ path: '**', component: NotFoundComponent },
];Navigating Programmatically
Use the Router service to navigate from TypeScript code — after form submission, login, or any async action.
import { Component, inject } from '@angular/core';
import { Router } from '@angular/router';
@Component({ standalone: true, template: `...` })
export class LoginComponent {
private router = inject(Router);
async onLogin() {
// ... authenticate
// Navigate to a path string
this.router.navigate(['/dashboard']);
// Navigate with route params
this.router.navigate(['/users', userId]);
// Navigate with query params
this.router.navigate(['/search'], {
queryParams: { q: 'angular', page: 1 },
});
// Navigate relatively (from current route)
this.router.navigate(['../sibling'], { relativeTo: this.activatedRoute });
// Navigate and replace history (no back button)
this.router.navigate(['/home'], { replaceUrl: true });
}
}routerLink Directive
In templates, use the routerLink directive instead of href. It handles SPA navigation without a full page reload.
<!-- String path -->
<a routerLink="/home">Home</a>
<!-- Array path (useful for dynamic segments) -->
<a [routerLink]="['/users', user.id]">{{ user.name }}</a>
<!-- With query params -->
<a [routerLink]="['/search']" [queryParams]="{ q: 'angular' }">Search</a>
<!-- Relative navigation -->
<a [routerLink]="['../edit']">Edit</a>
<!-- routerLinkActive adds CSS class when route is active -->
<a routerLink="/home" routerLinkActive="active">Home</a>
<!-- exact match — only active for exactly /home, not /home/sub -->
<a
routerLink="/home"
routerLinkActive="active"
[routerLinkActiveOptions]="{ exact: true }"
>Home</a>Reading Route Data
import { Component, inject, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
@Component({ standalone: true, template: `<h1>{{ title }}</h1>` })
export class SettingsComponent implements OnInit {
private route = inject(ActivatedRoute);
title = '';
ngOnInit() {
// Static data from route config
this.title = this.route.snapshot.data['title'];
// Subscribe for dynamic data changes
this.route.data.subscribe(data => {
this.title = data['title'];
});
}
}Router Events
Subscribe to router events to implement loading spinners, analytics tracking, or scroll restoration.
import { Component, inject, OnInit } from '@angular/core';
import { Router, NavigationStart, NavigationEnd, NavigationError } from '@angular/router';
import { RouterOutlet } from '@angular/router';
@Component({
standalone: true,
imports: [RouterOutlet],
template: `
<div *ngIf="loading">Loading...</div>
<router-outlet />
`,
})
export class AppComponent implements OnInit {
private router = inject(Router);
loading = false;
ngOnInit() {
this.router.events.subscribe(event => {
if (event instanceof NavigationStart) this.loading = true;
if (event instanceof NavigationEnd) this.loading = false;
if (event instanceof NavigationError) this.loading = false;
});
}
}Named Router Outlets
A page can have multiple outlets — useful for a sidebar, a modal, or a secondary panel that navigates independently.
// Template with a named outlet
// <router-outlet name="sidebar"></router-outlet>
// Route config for a named outlet
const routes: Routes = [
{
path: 'help',
component: HelpComponent,
outlet: 'sidebar', // renders in the named outlet
},
];
// Navigate to a named outlet
this.router.navigate([{ outlets: { sidebar: ['help'] } }]);
// Clear a named outlet
this.router.navigate([{ outlets: { sidebar: null } }]);PathMatch Strategies
pathMatch | Behaviour |
|---|---|
prefix (default) | Route matches if path starts with the URL segment |
full | Route matches only if the entire URL matches the path exactly |
// 'full' required for redirecting the empty path
{ path: '', redirectTo: '/home', pathMatch: 'full' }
// 'prefix' (default) — matches /products AND /products/123
{ path: 'products', component: ProductsComponent }
// Watch out: this redirects EVERYTHING because '' is a prefix of every path
// { path: '', redirectTo: '/home' } ← missing pathMatch: 'full'pathMatch: 'full' to empty-path redirects. Without it, the empty string matches every URL as a prefix, causing an infinite redirect loop.Hash vs HTML5 Location Strategy
import { bootstrapApplication } from '@angular/platform-browser';
import { provideRouter, withHashLocation } from '@angular/router';
// Default: HTML5 history API — clean URLs like /products/123
bootstrapApplication(AppComponent, {
providers: [provideRouter(routes)],
});
// Hash-based — URLs like /#/products/123 (useful for static hosts)
bootstrapApplication(AppComponent, {
providers: [provideRouter(routes, withHashLocation())],
});index.html for all routes — otherwise users will get 404 errors on direct navigation or refresh.Route Configuration Checklist
Wildcard route (**) must always be the last entry in the routes array
Empty-path redirects must have pathMatch: "full"
Lazy routes use loadComponent (single page) or loadChildren (feature area)
RouterOutlet must be imported in every component that uses it
RouterLink, RouterLinkActive must be imported too (standalone components)
Configure the server to serve index.html for all routes with HTML5 history
Summary: Angular's router maps URL paths to components via a Routes array. Use routerLink in templates and Router.navigate() in code to move between pages. Lazy loading splits the bundle, guards protect routes, and route parameters pass dynamic data. The next pages cover each of these topics in depth.