Part 10: Routing & Navigation – Building Single-Page Application Flows

Welcome to our comprehensive guide on Angular Routing! This powerful system enables seamless navigation while maintaining the single-page application experience.

Why Angular Router?

  • Creates bookmarkable URLs
  • Enables browser history navigation
  • Supports lazy loading
  • Provides route guards for security
  • Maintains application state

Basic Routing Setup

  1. First, import RouterModule:

typescript

// app.module.ts
import { RouterModule } from '@angular/router';

@NgModule({
  imports: [
    RouterModule.forRoot([
      { path: 'home', component: HomeComponent },
      { path: 'about', component: AboutComponent },
      { path: '', redirectTo: '/home', pathMatch: 'full' }
    ])
  ]
})
  1. Add the router outlet in your root template:

html

<!-- app.component.html -->
<app-header></app-header>
<router-outlet></router-outlet>  <!-- Components render here -->
<app-footer></app-footer>

Navigation Between Routes

Use routerLink for template navigation:

html

<nav>
  <a routerLink="/home" routerLinkActive="active">Home</a>
  <a routerLink="/about" routerLinkActive="active">About</a>
</nav>

Or navigate programmatically:

typescript

// component.ts
constructor(private router: Router) {}

navigateToAbout() {
  this.router.navigate(['/about']);
}

Route Parameters

1. Defining Routes with Parameters

typescript

{ path: 'products/:id', component: ProductDetailComponent }

2. Accessing Route Parameters

typescript

// Using ActivatedRoute
constructor(private route: ActivatedRoute) {}

ngOnInit() {
  this.productId = this.route.snapshot.paramMap.get('id');
  
  // For observable approach (react to changes):
  this.route.paramMap.subscribe(params => {
    this.productId = params.get('id');
  });
}

3. Generating Links with Parameters

html

<a [routerLink]="['/products', product.id]">View Product</a>

typescript

// Programmatic navigation
this.router.navigate(['/products', productId]);

Child Routes (Nested Views)

Create nested UI layouts:

typescript

{
  path: 'admin',
  component: AdminLayoutComponent,
  children: [
    { path: 'dashboard', component: DashboardComponent },
    { path: 'users', component: UserListComponent }
  ]
}

html

<!-- AdminLayoutComponent template -->
<h2>Admin Panel</h2>
<nav>
  <a routerLink="dashboard">Dashboard</a>
  <a routerLink="users">Users</a>
</nav>
<router-outlet></router-outlet>  <!-- Nested components appear here -->

Route Guards

Control route access:

1. CanActivate (Authentication Guard)

typescript

@Injectable()
export class AuthGuard implements CanActivate {
  constructor(private authService: AuthService, private router: Router) {}

  canActivate(): boolean {
    if (this.authService.isLoggedIn()) {
      return true;
    }
    this.router.navigate(['/login']);
    return false;
  }
}

2. CanDeactivate (Unsaved Changes Guard)

typescript

@Injectable()
export class UnsavedChangesGuard implements CanDeactivate<CanComponentDeactivate> {
  canDeactivate(component: CanComponentDeactivate) {
    return component.canDeactivate ? component.canDeactivate() : true;
  }
}

3. Registering Guards

typescript

{
  path: 'profile',
  component: ProfileComponent,
  canActivate: [AuthGuard],
  canDeactivate: [UnsavedChangesGuard]
}

Lazy Loading Modules

Dramatically improve initial load time:

typescript

{
  path: 'dashboard',
  loadChildren: () => import('./dashboard/dashboard.module')
                     .then(m => m.DashboardModule)
}

Advanced Routing Features

1. Route Resolvers (Pre-fetching Data)

typescript

@Injectable()
export class ProductResolver implements Resolve<Product> {
  constructor(private productService: ProductService) {}

  resolve(route: ActivatedRouteSnapshot) {
    return this.productService.getProduct(route.paramMap.get('id'));
  }
}

2. Custom Route Matchers

typescript

{
  matcher: (url) => {
    if (url[0].path.startsWith('product-')) {
      return {
        consumed: url,
        posParams: {
          id: new UrlSegment(url[0].path.substr(8), {})
        }
      };
    }
    return null;
  },
  component: ProductDetailComponent
}

3. Router Events

typescript

constructor(private router: Router) {
  this.router.events.pipe(
    filter(event => event instanceof NavigationEnd)
  ).subscribe(event => {
    console.log('Navigation ended:', event);
  });
}

Best Practices

  1. Organize routes in a dedicated routing module
  2. Use lazy loading for feature modules
  3. Protect routes with appropriate guards
  4. Handle 404s with a wildcard route
  5. Preload strategies for better UX

Hands-On Exercise

Build a complete routing setup with:

  1. Authentication-protected admin section
  2. Product detail pages with resolvers
  3. Lazy-loaded feature modules
  4. 404 page for unmatched routes

What’s Next?

In Part 11, we’ll explore Forms in Angular – both template-driven and reactive approaches!

Leave a Comment

Your email address will not be published. Required fields are marked *