Angular
04 / 10

Routing & Navigation

Angular Routing & Navigation

Angular Router enables navigation from one view to the next as users perform tasks. It interprets browser URLs and navigates to client-generated views.

Router Setup

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { AboutComponent } from './about/about.component';
import { UserListComponent } from './users/user-list.component';
import { UserDetailComponent } from './users/user-detail.component';
import { NotFoundComponent } from './not-found/not-found.component';

const routes: Routes = [
  { path: '', redirectTo: '/home', pathMatch: 'full' },
  { path: 'home', component: HomeComponent },
  { path: 'about', component: AboutComponent },
  { 
    path: 'users', 
    component: UserListComponent,
    children: [
      { path: ':id', component: UserDetailComponent }
    ]
  },
  { path: '**', component: NotFoundComponent } // Wildcard route
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

// Standalone app setup
import { provideRouter } from '@angular/router';
import { bootstrapApplication } from '@angular/platform-browser';

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

Navigation

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

@Component({
  selector: 'app-nav',
  template: `
    <!-- Declarative navigation -->
    <nav>
      <a routerLink="/home" routerLinkActive="active">Home</a>
      <a routerLink="/about" routerLinkActive="active">About</a>
      <a [routerLink]="['/users', userId]" routerLinkActive="active">User</a>
      <a routerLink="/users/123" [queryParams]="{tab: 'posts'}" 
         [fragment]="'comments'">User with Query</a>
    </nav>
    
    <button (click)="navigateToUser()">Go to User</button>
    <button (click)="goBack()">Go Back</button>
    
    <!-- Router outlet -->
    <router-outlet></router-outlet>
  `
})
export class NavComponent {
  userId = 123;
  
  constructor(
    private router: Router,
    private route: ActivatedRoute
  ) { }
  
  navigateToUser() {
    // Programmatic navigation
    this.router.navigate(['/users', this.userId], {
      queryParams: { tab: 'posts' },
      fragment: 'comments'
    });
  }
  
  goBack() {
    window.history.back();
  }
}

Route Parameters

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, ParamMap } from '@angular/router';
import { switchMap } from 'rxjs/operators';

@Component({
  selector: 'app-user-detail',
  template: `
    <div *ngIf="user">
      <h2>{{ user.name }}</h2>
      <p>{{ user.email }}</p>
      <p>Tab: {{ activeTab }}</p>
    </div>
  `
})
export class UserDetailComponent implements OnInit {
  user: any;
  activeTab: string = '';
  
  constructor(
    private route: ActivatedRoute,
    private userService: UserService
  ) { }
  
  ngOnInit() {
    // Snapshot - one-time access
    const id = this.route.snapshot.paramMap.get('id');
    const tab = this.route.snapshot.queryParamMap.get('tab');
    const fragment = this.route.snapshot.fragment;
    
    // Observable - reactive to changes
    this.route.paramMap.pipe(
      switchMap((params: ParamMap) => {
        const userId = params.get('id');
        return this.userService.getUser(userId);
      })
    ).subscribe(user => this.user = user);
    
    // Query params
    this.route.queryParams.subscribe(params => {
      this.activeTab = params['tab'] || 'profile';
    });
    
    // Fragment
    this.route.fragment.subscribe(fragment => {
      if (fragment) {
        document.getElementById(fragment)?.scrollIntoView();
      }
    });
  }
}

Route Guards

import { Injectable } from '@angular/core';
import { CanActivate, CanActivateChild, CanDeactivate, 
         ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';
import { Observable } from 'rxjs';

// CanActivate - protect route access
@Injectable({ providedIn: 'root' })
export class AuthGuard implements CanActivate {
  constructor(
    private authService: AuthService,
    private router: Router
  ) { }
  
  canActivate(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): boolean | Observable<boolean> {
    if (this.authService.isLoggedIn()) {
      return true;
    }
    
    // Store intended URL
    this.router.navigate(['/login'], {
      queryParams: { returnUrl: state.url }
    });
    return false;
  }
}

// CanActivateChild - protect child routes
@Injectable({ providedIn: 'root' })
export class AdminGuard implements CanActivateChild {
  constructor(private authService: AuthService) { }
  
  canActivateChild(): boolean {
    return this.authService.isAdmin();
  }
}

// CanDeactivate - prevent leaving route
export interface CanComponentDeactivate {
  canDeactivate: () => boolean | Observable<boolean>;
}

@Injectable({ providedIn: 'root' })
export class UnsavedChangesGuard implements CanDeactivate<CanComponentDeactivate> {
  canDeactivate(
    component: CanComponentDeactivate
  ): boolean | Observable<boolean> {
    return component.canDeactivate ? component.canDeactivate() : true;
  }
}

// Component implementing CanComponentDeactivate
@Component({
  selector: 'app-edit-user'
})
export class EditUserComponent implements CanComponentDeactivate {
  hasUnsavedChanges = false;
  
  canDeactivate(): boolean {
    if (this.hasUnsavedChanges) {
      return confirm('You have unsaved changes. Leave anyway?');
    }
    return true;
  }
}

// Apply guards to routes
const routes: Routes = [
  {
    path: 'admin',
    component: AdminComponent,
    canActivate: [AuthGuard],
    canActivateChild: [AdminGuard],
    children: [
      { path: 'users', component: UsersComponent },
      { 
        path: 'edit/:id', 
        component: EditUserComponent,
        canDeactivate: [UnsavedChangesGuard]
      }
    ]
  }
];

Lazy Loading

const routes: Routes = [
  {
    path: 'users',
    loadChildren: () => import('./users/users.module').then(m => m.UsersModule)
  },
  {
    path: 'admin',
    loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule),
    canLoad: [AuthGuard] // Prevent loading module
  }
];

// users-routing.module.ts
const routes: Routes = [
  { path: '', component: UserListComponent },
  { path: ':id', component: UserDetailComponent }
];

@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule]
})
export class UsersRoutingModule { }

Resolver

import { Injectable } from '@angular/core';
import { Resolve, ActivatedRouteSnapshot } from '@angular/router';
import { Observable } from 'rxjs';

@Injectable({ providedIn: 'root' })
export class UserResolver implements Resolve<User> {
  constructor(private userService: UserService) { }
  
  resolve(route: ActivatedRouteSnapshot): Observable<User> {
    const id = route.paramMap.get('id');
    return this.userService.getUser(id);
  }
}

// Use in route
const routes: Routes = [
  {
    path: 'users/:id',
    component: UserDetailComponent,
    resolve: { user: UserResolver }
  }
];

// Access resolved data in component
@Component({ /* ... */ })
export class UserDetailComponent implements OnInit {
  user: User;
  
  constructor(private route: ActivatedRoute) { }
  
  ngOnInit() {
    this.user = this.route.snapshot.data['user'];
    
    // Or with observable
    this.route.data.subscribe(data => {
      this.user = data['user'];
    });
  }
}

Keep your own version of these notes — editable, searchable, and organised by your stack.

Start free