Lesson 04-Angular Routing and Navigation

Routing Basics

Angular routing enables users to navigate between different views of an application without reloading the page. It is component-based and provides a robust API for defining routing rules, handling navigation logic, and passing parameters.

Installing and Importing RouterModule

Ensure the @angular/router package is installed in your Angular project, then import RouterModule and Routes in the root module.

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

const appRoutes: Routes = [
  { path: '', component: HomeComponent },
  { path: 'about', component: AboutComponent },
  { path: 'contact', component: ContactComponent }
];

@NgModule({
  imports: [
    RouterModule.forRoot(appRoutes),
    // Other modules...
  ],
  // ...
})
export class AppModule { }

Setting Up the Router Outlet

In app.component.html, use the <router-outlet> tag to specify where routed content should be displayed.

<!-- app.component.html -->
<header>
  <!-- Navigation menu -->
</header>
<main>
  <router-outlet></router-outlet>
</main>
<footer>
  <!-- Footer -->
</footer>

Configuring Route Parameters

Route parameters include path parameters (:id) and query parameters (?queryParam=value). Define path parameters in the route configuration with a colon prefix.

const appRoutes: Routes = [
  { path: 'user/:id', component: UserComponent },
  // ...
];

Access these parameters in a component using the ActivatedRoute service.

// user.component.ts
import { ActivatedRoute } from '@angular/router';

constructor(private route: ActivatedRoute) {
  this.route.params.subscribe(params => {
    const userId = params['id'];
    // ...
  });
}

Route Guards

Route guards control access to routes, with common guards including CanActivate and CanDeactivate.

// auth.guard.ts
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
  return this.authService.isLoggedIn() ? true : this.router.createUrlTree(['/login']);
}

// Apply the guard in route configuration
const routes: Routes = [
  { path: 'admin', component: AdminComponent, canActivate: [AuthGuard] },
  // ...
];

Child Routes

Child routes allow defining a subset of routes within a parent component.

const appRoutes: Routes = [
  { path: 'dashboard', component: DashboardComponent, children: [
    { path: 'settings', component: SettingsComponent },
    // ...
  ]},
  // ...
];

Use a nested <router-outlet> in the parent component.

<!-- dashboard.component.html -->
<h2>Dashboard</h2>
<router-outlet></router-outlet>

Route Redirects and Empty Paths

  • Redirect: Use the redirectTo property.
  • Empty Path: Typically used for the default route.
const appRoutes: Routes = [
  { path: '', redirectTo: '/home', pathMatch: 'full' },
  { path: 'home', component: HomeComponent },
  // ...
];

Route Events and Navigation

Use the Router service for manual navigation or to listen to route events.

// Manual navigation
this.router.navigate(['/about']);

// Listen to route events
this.router.events.subscribe(event => {
  if (event instanceof NavigationEnd) {
    console.log('Navigation ended', event.urlAfterRedirects);
  }
});

Dynamic Routing and Route Activation

Dynamically generate route configurations or use RouteReuseStrategy to control component instance reuse.

Route Animations

Combine with @angular/animations to add transition effects for route changes.

Route Preloading

Preloading strategies load components at application startup or before navigation, improving user experience by reducing wait times.

// app.routing.module.ts
import { PreloadAllModules, Route } from '@angular/router';

const appRoutes: Routes = [
  // Route configurations...
];

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

Lazy Loading

Lazy loading delays module loading until the user navigates to the corresponding route, reducing initial load time and improving performance.

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

Route Metadata

Add metadata to route configurations for extra information, such as titles or descriptions.

const routes: Routes = [
  {
    path: 'product',
    component: ProductComponent,
    data: { title: 'Product Page', description: 'View our products.' }
  },
  // ...
];

Use the routerLink directive to create navigation links without JavaScript.

<a routerLink="/about">About Us</a>

Route Resolvers

Resolvers fetch data before activating a route, ensuring components have necessary data upon rendering.

// product.resolver.ts
import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { ProductService } from './product.service';

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

  resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Product> {
    return this.productService.getProductById(route.params['id']);
  }
}

Apply the Resolver in Route Configuration:

const routes: Routes = [
  {
    path: 'product/:id',
    component: ProductComponent,
    resolve: { product: ProductResolver }
  },
  // ...
];

Parameter Passing and Route Guards

Parameter Passing

Path Parameters

Path parameters are embedded in the URL to identify specific resources, e.g., /users/:userId where :userId is the parameter.

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

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

Access path parameters in a component using ActivatedRoute.

// user-detail.component.ts
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

@Component({
  selector: 'app-user-detail',
  template: `<h1>User Detail: {{ userId }}</h1>`
})
export class UserDetailComponent implements OnInit {
  userId: string;

  constructor(private route: ActivatedRoute) { }

  ngOnInit() {
    this.userId = this.route.snapshot.paramMap.get('userId');
  }
}

Query Parameters

Query parameters appear in the URL after a question mark, e.g., /search?query=angular, where query is the parameter.

// search.component.ts
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

@Component({
  selector: 'app-search',
  template: `<h1>Search Results for: {{ query }}</h1>`
})
export class SearchComponent implements OnInit {
  query: string;

  constructor(private route: ActivatedRoute) { }

  ngOnInit() {
    this.query = this.route.snapshot.queryParams['query'];
  }
}

Route Data

Route data is metadata attached to route configurations, useful for component initialization.

// app.routing.module.ts
const routes: Routes = [
  {
    path: 'about',
    component: AboutComponent,
    data: { title: 'About Us' }
  }
];

Access route data via ActivatedRoute’s data property.

// about.component.ts
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

@Component({
  selector: 'app-about',
  template: `<h1>{{ title }}</h1>`
})
export class AboutComponent implements OnInit {
  title: string;

  constructor(private route: ActivatedRoute) { }

  ngOnInit() {
    this.title = this.route.snapshot.data['title'];
  }
}

Route Guards

Route guards control access to specific routes, with the following types:

  • CanActivate: Runs before route activation to protect routes.
  • CanDeactivate: Runs before leaving a route to confirm navigation.
  • Resolve: Runs before route activation to preload data.

CanActivate

// auth.guard.ts
import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';
import { AuthService } from './auth.service';

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

  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
    if (this.authService.isLoggedIn()) {
      return true;
    } else {
      this.router.navigate(['/login']);
      return false;
    }
  }
}

Apply the Guard in Route Configuration:

// app.routing.module.ts
const routes: Routes = [
  {
    path: 'admin',
    component: AdminComponent,
    canActivate: [AuthGuard]
  }
];

CanDeactivate

// can-deactivate.guard.ts
import { Injectable } from '@angular/core';
import { CanDeactivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { CanDeactivateComponent } from './can-deactivate.component';

@Injectable()
export class CanDeactivateGuard implements CanDeactivate<CanDeactivateComponent> {
  canDeactivate(component: CanDeactivateComponent, currentRoute: ActivatedRouteSnapshot, currentState: RouterStateSnapshot, nextState?: RouterStateSnapshot): boolean {
    return component.canDeactivate();
  }
}

Implement the CanDeactivate Interface in the Component:

// can-deactivate.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-can-deactivate',
  template: '<h1>Can Deactivate Component</h1>'
})
export class CanDeactivateComponent {
  canDeactivate(): boolean {
    return window.confirm('Are you sure you want to leave?');
  }
}

Apply the Guard in Route Configuration:

// app.routing.module.ts
const routes: Routes = [
  {
    path: 'can-deactivate',
    component: CanDeactivateComponent,
    canDeactivate: [CanDeactivateGuard]
  }
];

Resolve

// data-resolver.service.ts
import { Injectable } from '@angular/core';
import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { DataResolverService } from './data-resolver.service';

@Injectable()
export class DataResolver implements Resolve<any> {
  constructor(private dataResolverService: DataResolverService) {}

  resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any> {
    return this.dataResolverService.getData();
  }
}

Apply the Resolver in Route Configuration:

// app.routing.module.ts
const routes: Routes = [
  {
    path: 'data',
    component: DataComponent,
    resolve: {
      data: DataResolver
    }
  }
];

Access Resolved Data in the Component:

// data.component.ts
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

@Component({
  selector: 'app-data',
  template: '<h1>Data: {{ data }}</h1>'
})
export class DataComponent implements OnInit {
  data: any;

  constructor(private route: ActivatedRoute) {}

  ngOnInit() {
    this.data = this.route.snapshot.data['data'];
  }
}

Dynamic Routing and Nested Routing

Dynamic Routing

Dynamic routing enables loading components based on URL parameters, commonly used for displaying resources like user profiles or product details.

Configuring Dynamic Routes

Dynamic routes use path parameters, denoted by a colon prefix, e.g., /:id.

// app.routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { UserDetailComponent } from './user-detail/user-detail.component';

const routes: Routes = [
  { path: 'users/:id', component: UserDetailComponent },
  // Other routes...
];

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

Accessing Dynamic Parameters

Access path parameters in a component using ActivatedRoute.

// user-detail.component.ts
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

@Component({
  selector: 'app-user-detail',
  templateUrl: './user-detail.component.html',
  styleUrls: ['./user-detail.component.css']
})
export class UserDetailComponent implements OnInit {
  userId: string;

  constructor(private route: ActivatedRoute) { }

  ngOnInit() {
    this.userId = this.route.snapshot.paramMap.get('id');
  }
}

Nested Routing

Nested routing allows defining multiple routes within a parent component, enabling multi-level navigation structures, such as “Settings” and “Profile” within a “Dashboard” component.

Configuring Nested Routes

Use the children property in the parent route configuration to define child routes.

// app.routing.module.ts
const routes: Routes = [
  {
    path: 'dashboard',
    component: DashboardComponent,
    children: [
      { path: 'settings', component: SettingsComponent },
      { path: 'profile', component: ProfileComponent }
    ]
  },
  // Other routes...
];

Using Nested Routes

Render child route content in the parent component using <router-outlet>.

<!-- dashboard.component.html -->
<div class="dashboard">
  <nav>
    <a routerLink="/dashboard/settings">Settings</a>
    <a routerLink="/dashboard/profile">Profile</a>
  </nav>
  <router-outlet></router-outlet>
</div>

Combining Dynamic Loading with Nested Routes

Combining lazy loading with nested routes enhances performance by loading components only when needed.

// app.routing.module.ts
const routes: Routes = [
  {
    path: 'dashboard',
    loadChildren: () => import('./dashboard/dashboard.module').then(m => m.DashboardModule),
    children: [
      { path: 'settings', component: SettingsComponent },
      { path: 'profile', component: ProfileComponent }
    ]
  },
  // Other routes...
];

Complex Nested Routing

In large applications, multi-level nested routes may be required. For example, a “Users” section might include “List,” “Details,” and “Edit” pages, with “Details” containing “Photos” and “Comments.”

const routes: Routes = [
  {
    path: 'users',
    component: UsersComponent,
    children: [
      { path: '', component: UsersListComponent },
      {
        path: ':id',
        component: UserDetailsComponent,
        children: [
          { path: 'photos', component: PhotosComponent },
          { path: 'comments', component: CommentsComponent }
        ]
      },
      { path: ':id/edit', component: EditUserComponent }
    ]
  },
  // Other routes...
];

Route Redirects

Redirect users from one route to another, e.g., redirecting unauthenticated users to a login page.

const routes: Routes = [
  {
    path: 'protected',
    component: ProtectedComponent,
    canActivate: [AuthGuard],
    children: [
      { path: 'details', component: DetailsComponent }
    ]
  },
  { path: '', redirectTo: '/login', pathMatch: 'full' }
];

Route Guards with Dynamic Routes

Combine route guards with dynamic routes for fine-grained access control, e.g., restricting user edit pages to administrators.

const routes: Routes = [
  {
    path: 'admin/users/:id/edit',
    component: EditUserComponent,
    canActivate: [AdminGuard]
  }
];

Utilizing Route Data

Route data is a powerful feature for passing additional information to components, such as page titles or SEO metadata.

const routes: Routes = [
  {
    path: 'product/:id',
    component: ProductComponent,
    data: { title: 'Product Detail', breadcrumb: 'Product' }
  }
];

Access route data via ActivatedRoute’s data property.

// product.component.ts
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

@Component({
  selector: 'app-product',
  templateUrl: './product.component.html'
})
export class ProductComponent implements OnInit {
  title: string;

  constructor(private route: ActivatedRoute) { }

  ngOnInit() {
    this.title = this.route.snapshot.data['title'];
  }
}

Route Resolvers

Resolvers preload data before component activation, improving user experience by avoiding delays from asynchronous data requests.

// product.resolver.ts
import { Injectable } from '@angular/core';
import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { ProductService } from './product.service';

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

  resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Product {
    return this.productService.getProductById(route.params['id']);
  }
}

Apply the Resolver in Route Configuration:

const routes: Routes = [
  {
    path: 'product/:id',
    component: ProductComponent,
    resolve: { product: ProductResolver }
  }
];
Share your love