Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Chapter 8: Routing and Navigation

Most useful applications aren’t made up of a single screen. They involve multiple views: a dashboard, a list of users, a detail page, a settings panel. To move between these, we need a routing system.

Angular provides a powerful, built-in Router that allows you to:

  • Define application routes.
  • Navigate between views.
  • Pass parameters.
  • Protect certain routes with guards.
  • Optimize loading with lazy modules or standalone APIs.

In this chapter, we’ll build a strong understanding of Angular’s routing system, from the basics to advanced features.


8.1 Setting Up Routing

When generating a new Angular project, the CLI asks:

? Would you like to add Angular routing? (y/N)

If you answer Yes, it configures RouterModule automatically. If not, you can add it later:

import { provideRouter, Routes } from '@angular/router';
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';

const routes: Routes = [];

bootstrapApplication(AppComponent, {
  providers: [provideRouter(routes)]
});

Here we’re using the standalone API (provideRouter), which is the modern way to set up routing.


8.2 Defining Routes

A route maps a URL path to a component.

import { Routes } from '@angular/router';
import { HomeComponent } from './home.component';
import { AboutComponent } from './about.component';

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

In your root template (app.component.html), add the <router-outlet> directive:

<nav>
  <a routerLink="">Home</a>
  <a routerLink="about">About</a>
</nav>

<router-outlet></router-outlet>

Now your app has two navigable views.


8.3 Navigating Between Routes

There are three ways to navigate:

  1. Declarative links (preferred for most cases):
<a routerLink="/about">About</a>
  1. Programmatic navigation using Router:
import { Router } from '@angular/router';

@Component({ /* ... */ })
export class HomeComponent {
  constructor(private router: Router) {}

  goToAbout() {
    this.router.navigate(['/about']);
  }
}
  1. Relative navigation (relative to current route):
this.router.navigate(['../profile'], { relativeTo: this.route });

8.4 Route Parameters

Sometimes we need dynamic routes, like /users/42.

{ path: 'users/:id', component: UserDetailComponent }

In UserDetailComponent, you can access the parameter:

import { Component } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

@Component({
  selector: 'app-user-detail',
  standalone: true,
  template: `<h2>User ID: {{ userId }}</h2>`
})
export class UserDetailComponent {
  userId!: string;

  constructor(private route: ActivatedRoute) {
    this.userId = this.route.snapshot.paramMap.get('id')!;
  }
}

For reactive updates (when navigating between users while staying on the same page):

this.route.paramMap.subscribe(params => {
  this.userId = params.get('id')!;
});

8.5 Query Parameters and Fragments

Add optional data to a URL with query parameters:

<a [routerLink]="['/search']" [queryParams]="{ q: 'angular' }">
  Search Angular
</a>

In the component:

this.route.queryParamMap.subscribe(params => {
  console.log(params.get('q')); // "angular"
});

Fragments (#anchor) are also supported via this.route.fragment.


8.6 Nested Routes (Child Routes)

You can define child routes inside a parent.

{ 
  path: 'admin', 
  component: AdminComponent,
  children: [
    { path: 'users', component: AdminUsersComponent },
    { path: 'settings', component: AdminSettingsComponent }
  ]
}

<router-outlet> inside AdminComponent renders the child views.


8.7 Lazy Loading

For large apps, it’s inefficient to load everything up front. Lazy loading lets Angular load features only when needed.

With standalone APIs:

{
  path: 'products',
  loadComponent: () =>
    import('./products/products.component').then(m => m.ProductsComponent)
}

This way, the Products feature is only downloaded when the user navigates to /products.


8.8 Route Guards

Some routes should only be accessible under certain conditions — e.g., if the user is logged in. Guards let you protect routes.

Example AuthGuard:

import { inject } from '@angular/core';
import { CanActivateFn, Router } from '@angular/router';
import { AuthService } from './auth.service';

export const authGuard: CanActivateFn = () => {
  const auth = inject(AuthService);
  const router = inject(Router);

  return auth.isLoggedIn() ? true : router.parseUrl('/login');
};

Use it in routes:

{ path: 'dashboard', component: DashboardComponent, canActivate: [authGuard] }

8.9 Route Resolvers

Resolvers let you fetch data before navigation completes, ensuring the component has the data it needs immediately.

import { ResolveFn } from '@angular/router';
import { UserService } from './user.service';

export const userResolver: ResolveFn<any> = (route) => {
  const service = inject(UserService);
  return service.getUser(route.paramMap.get('id')!);
};

Route definition:

{ path: 'users/:id', component: UserDetailComponent, resolve: { user: userResolver } }

Inside the component:

this.route.data.subscribe(data => {
  this.user = data['user'];
});

8.10 Best Practices

  • Keep routes organized by feature.
  • Prefer standalone route definitions (provideRouter).
  • Use lazy loading for large features.
  • Use guards for security, not just UI (still validate on the backend).
  • Avoid deeply nested routes unless truly necessary.

8.11 Summary

  • Angular’s router lets you define paths, navigate, and manage dynamic data in URLs.
  • Routing works with standalone APIs, eliminating the need for NgModules.
  • Features include parameters, query params, nested routes, lazy loading, guards, and resolvers.