Routing & Navigation

Angular Routing Basics

Learn how to set up Angular Router, define routes, navigate between components, and pass data through route parameters.

What is Routing?

Routing maps URLs to components, enabling navigation between different views in your single-page application (SPA). Angular's built-in router handles:

  • URL navigation without full page reloads
  • Route parameters and query parameters
  • Nested (child) routes
  • Lazy loading of modules
  • Route guards for access control

Setting Up the Router

Define Routes

typescript
// app.routes.ts
import { Routes } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { AboutComponent } from './about/about.component';
import { ContactComponent } from './contact/contact.component';

export const routes: Routes = [
  { path: '', component: HomeComponent },
  { path: 'about', component: AboutComponent },
  { path: 'contact', component: ContactComponent },
  { path: '**', redirectTo: '' }, // Wildcard: redirect unknown routes to home
];

Provide Router in App Config

typescript
// app.config.ts
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes),
  ],
};

Add Router Outlet

The <router-outlet> is where routed components are rendered:

typescript
// app.component.ts
import { Component } from '@angular/core';
import { RouterOutlet, RouterLink, RouterLinkActive } from '@angular/router';

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

    <main>
      <router-outlet />
    </main>
  `,
  styles: [`.active { color: blue; font-weight: bold; }`],
})
export class AppComponent {}

Template Navigation with routerLink

html
<!-- Static links -->
<a routerLink="/products">Products</a>
<a routerLink="/products/42">Product #42</a>

<!-- Dynamic links using array syntax -->
<a [routerLink]="['/products', product.id]">{{ product.name }}</a>
<a [routerLink]="['/search']" [queryParams]="{ q: searchTerm }">
  Search
</a>

<!-- With query parameters and fragments -->
<a routerLink="/docs"
   [queryParams]="{ page: 2, sort: 'name' }"
   fragment="section-1">
  Docs Page 2
</a>
<!-- Results in: /docs?page=2&sort=name#section-1 -->

Programmatic Navigation

typescript
import { Router } from '@angular/router';

@Component({ /* ... */ })
export class LoginComponent {
  private router = inject(Router);

  onLogin() {
    // Simple navigation
    this.router.navigate(['/dashboard']);

    // With route params
    this.router.navigate(['/users', userId]);

    // With query params
    this.router.navigate(['/search'], {
      queryParams: { q: 'angular', page: 1 },
    });

    // Navigate by URL string
    this.router.navigateByUrl('/dashboard/settings');
  }
}

Route Parameters

Define Route with Parameter

typescript
const routes: Routes = [
  { path: 'users', component: UserListComponent },
  { path: 'users/:id', component: UserDetailComponent },
  { path: 'products/:category/:id', component: ProductComponent },
];

Read Route Parameters

typescript
import { Component, inject, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

@Component({
  selector: 'app-user-detail',
  standalone: true,
  template: `
    <h2>User #{{ userId }}</h2>
    <!-- user details -->
  `,
})
export class UserDetailComponent implements OnInit {
  private route = inject(ActivatedRoute);
  userId!: string;

  ngOnInit() {
    // Snapshot (gets the value once)
    this.userId = this.route.snapshot.params['id'];

    // Observable (reacts to changes when same component reused)
    this.route.params.subscribe(params => {
      this.userId = params['id'];
      this.loadUser(this.userId);
    });
  }

  private loadUser(id: string) {
    // Fetch user data
  }
}

Using input() for Route Params (Angular 16+)

typescript
// Enable in app config
provideRouter(routes, withComponentInputBinding())

// Component receives route params as inputs automatically
@Component({ /* ... */ })
export class UserDetailComponent {
  @Input() id!: string; // Automatically bound from :id route param

  ngOnInit() {
    console.log('User ID:', this.id);
  }
}

Query Parameters

typescript
// Navigate with query params
this.router.navigate(['/search'], {
  queryParams: { q: 'angular', category: 'frameworks' },
});
// URL: /search?q=angular&category=frameworks

// Read query params
export class SearchComponent implements OnInit {
  private route = inject(ActivatedRoute);

  ngOnInit() {
    // Snapshot
    const query = this.route.snapshot.queryParams['q'];

    // Observable
    this.route.queryParams.subscribe(params => {
      console.log('Search query:', params['q']);
      console.log('Category:', params['category']);
    });
  }
}

Child Routes (Nested)

Group related routes under a parent:

typescript
const routes: Routes = [
  {
    path: 'admin',
    component: AdminLayoutComponent,
    children: [
      { path: '', component: AdminDashboardComponent },
      { path: 'users', component: AdminUsersComponent },
      { path: 'users/:id', component: AdminUserEditComponent },
      { path: 'settings', component: AdminSettingsComponent },
    ],
  },
];

The parent component needs its own <router-outlet>:

typescript
@Component({
  selector: 'app-admin-layout',
  standalone: true,
  imports: [RouterOutlet, RouterLink],
  template: `
    <div class="admin-layout">
      <aside class="sidebar">
        <a routerLink="/admin">Dashboard</a>
        <a routerLink="/admin/users">Users</a>
        <a routerLink="/admin/settings">Settings</a>
      </aside>
      <main class="content">
        <router-outlet />
      </main>
    </div>
  `,
})
export class AdminLayoutComponent {}

Redirects and Wildcards

typescript
const routes: Routes = [
  { path: '', redirectTo: '/home', pathMatch: 'full' },
  { path: 'home', component: HomeComponent },
  { path: 'old-about', redirectTo: '/about' },
  { path: '**', component: NotFoundComponent }, // 404 page
];

Important: The wildcard route (**) must be the last route โ€” Angular matches routes in order.

Route Data

Pass static data to a route:

typescript
const routes: Routes = [
  {
    path: 'dashboard',
    component: DashboardComponent,
    data: {
      title: 'Dashboard',
      requiresAuth: true,
    },
  },
];

// Read in component
export class DashboardComponent {
  private route = inject(ActivatedRoute);

  ngOnInit() {
    const title = this.route.snapshot.data['title'];
    document.title = title;
  }
}
html
<nav>
  <a routerLink="/home"
     routerLinkActive="active"
     [routerLinkActiveOptions]="{ exact: true }">
    Home
  </a>
  <a routerLink="/products" routerLinkActive="active">Products</a>
  <a routerLink="/about" routerLinkActive="active">About</a>
</nav>
css
.active {
  color: #1976d2;
  font-weight: 600;
  border-bottom: 2px solid #1976d2;
}

Next Steps

Routes need protection โ€” not every user should access every page. Next, we'll learn about route guards to control navigation and protect routes based on authentication, roles, and conditions.