Back to all posts

Angular Router


Angular’s routing system is a powerful feature that enable navigation between different components in your application. This guide will walk you though all essential routing concepts with practical example

Basic Route Setup

Enabling Routing

First, you need to setup routing in your angular application. Create a dedicated routing module:

// app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { AboutComponent } from './about/about.component';

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

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

Add the router outlet to your main app component:

// app.component.html
<nav>
  <a routerLink="/">Home</a>
  <a routerLink="/about">About</a>
</nav>
<router-outlet></router-outlet>

Dynamic Routes

Setting Up Dynamic Routes

Dynamic routes allow you to pass parameters in the URL:

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

Extracting Route parameters

You can access route parameters in two ways:

  1. Using activatedRoute with Inputs:
      // user.component.ts
      export class UserComponent implements OnInit {
        userId: string;
      
        constructor(private route: ActivatedRoute) {}
      
        ngOnInit() {
          this.userId = this.route.snapshot.params['id'];
        }
      }

      2. Using Observables (recommended for dynamic changes):

      export class UserComponent implements OnInit {
        userId: string;
      
        constructor(private route: ActivatedRoute) {}
      
        ngOnInit() {
          this.route.params.subscribe(params => {
            this.userId = params['id'];
          });
        }
      }

      Nested Routes

      Nested routes allow you to create hierarchical navigation structures:

      const routes: Routes = [
        {
          path: 'users',
          component: UsersComponent,
          children: [
            { path: ':id', component: UserDetailComponent },
            { path: ':id/edit', component: UserEditComponent }
          ]
        }
      ];

      Parent component template:

      <!-- users.component.html -->
      <div class="users-container">
        <nav>
          <a [routerLink]="['/users', user.id]" *ngFor="let user of users">
            {{ user.name }}
          </a>
        </nav>
        <router-outlet></router-outlet>
      </div>

      Query Parameters

      Setting Query Parameters

      // In template
      <a [routerLink]="['/products']" [queryParams]="{ category: 'electronics', sort: 'price' }">
        Electronics
      </a>
      
      // Programmatically
      constructor(private router: Router) {}
      
      navigateToProducts() {
        this.router.navigate(['/products'], {
          queryParams: { category: 'electronics', sort: 'price' }
        });
      }

      Accessing Query Parameters

      export class ProductsComponent implements OnInit {
        constructor(private route: ActivatedRoute) {}
      
        ngOnInit() {
          // Using snapshot
          const category = this.route.snapshot.queryParams['category'];
      
          // Using observable
          this.route.queryParams.subscribe(params => {
            const category = params['category'];
            const sort = params['sort'];
          });
        }
      }

      Route Guards

      Route guards help protect routes based on certain conditions:

      // auth.guard.ts
      @Injectable({
        providedIn: 'root'
      })
      export class AuthGuard implements CanActivate {
        constructor(private authService: AuthService, private router: Router) {}
      
        canActivate(
          route: ActivatedRouteSnapshot,
          state: RouterStateSnapshot
        ): boolean | Promise<boolean> | Observable<boolean> {
          if (this.authService.isAuthenticated()) {
            return true;
          }
          
          this.router.navigate(['/login']);
          return false;
        }
      }

      Apply the guard to routes

      const routes: Routes = [
        {
          path: 'admin',
          component: AdminComponent,
          canActivate: [AuthGuard]
        }
      ];

      Route Resolvers

      Resolvers help load data before a route is activated:

      // user-resolver.service.ts
      @Injectable({
        providedIn: 'root'
      })
      export class UserResolver implements Resolve<User> {
        constructor(private userService: UserService) {}
      
        resolve(
          route: ActivatedRouteSnapshot,
          state: RouterStateSnapshot
        ): Observable<User> {
          return this.userService.getUser(route.params['id']);
        }
      }

      Configure the resolver in routes:

      const routes: Routes = [
        {
          path: 'user/:id',
          component: UserComponent,
          resolve: {
            user: UserResolver
          }
        }
      ];

      Access resolved data in component:

      export class UserComponent implements OnInit {
        user: User;
      
        constructor(private route: ActivatedRoute) {}
      
        ngOnInit() {
          this.route.data.subscribe(data => {
            this.user = data['user'];
          });
        }
      }

      Not Found Routes and Redirects

      Handle unknown routes and redirects:

      const routes: Routes = [
        { path: 'home', component: HomeComponent },
        { path: '', redirectTo: '/home', pathMatch: 'full' },
        { path: '**', component: NotFoundComponent }
      ];

      Route Titles

      Set dynamic titles for routes:

      const routes: Routes = [
        {
          path: 'product/:id',
          component: ProductComponent,
          title: 'Product Details',
          resolve: {
            product: ProductResolver
          },
          title: (route: ActivatedRouteSnapshot) => {
            const product = route.data['product'];
            return `${product.name} - Details`;
          }
        }
      ];

      Splitting Route Definitions

      For larger applications, split routes into feature modules:

      // feature-routing.module.ts
      const featureRoutes: Routes = [
        {
          path: 'feature',
          component: FeatureComponent,
          children: [
            { path: 'list', component: ListComponent },
            { path: 'detail/:id', component: DetailComponent }
          ]
        }
      ];
      
      @NgModule({
        imports: [RouterModule.forChild(featureRoutes)],
        exports: [RouterModule]
      })
      export class FeatureRoutingModule { }

      Best Practices and Tips

      1. Always use RouterLink directives instead of href attributes for internal navigation
      2. Implement proper error handling in resolvers
      3. Use child routes for related feature sets
      4. Implement loading indicators for async operations
      5. Consider using route guards for authentication and authorization
      6. Use proper typing for route parameters and query parameters
      7. Implement proper cleanup in components that subscribe to route observables

      Example: Complete Navigation Structure

      Here’s a complete example combining multiple concepts:

      const routes: Routes = [
        {
          path: '',
          component: LayoutComponent,
          children: [
            {
              path: '',
              redirectTo: 'dashboard',
              pathMatch: 'full'
            },
            {
              path: 'dashboard',
              component: DashboardComponent,
              title: 'Dashboard'
            },
            {
              path: 'users',
              loadChildren: () => import('./users/users.module')
                .then(m => m.UsersModule),
              canActivate: [AuthGuard]
            },
            {
              path: 'products',
              component: ProductsComponent,
              resolve: {
                products: ProductsResolver
              },
              children: [
                {
                  path: ':id',
                  component: ProductDetailComponent,
                  resolve: {
                    product: ProductResolver
                  }
                }
              ]
            }
          ]
        },
        {
          path: 'login',
          component: LoginComponent
        },
        {
          path: '**',
          component: NotFoundComponent
        }
      ];