Disqus Shortname

Responsive Advertisement

Angular the series Part 9: Routing

    In a Single Page Application, all your code exist in a single HTML page. When users access some specific features of the application, the browser will only render the parts that matter to the users instead of loading a new page. To define how users can navigate around our application, we can use routes. It provides us a mechanism to navigate from one part of our application to another. We can also configure routes to guard against unexpected behavior, authorize or pre-process some logic before loading the page.

    If you remember, when we create a new Angular project via AngularCLI, it will ask us if we would like to add Angular routing or not. If we choose Yes, then it will create a file called app-routing.module.ts inside /app folder. In this file we can tell Angular which component should be displayed for a specific .

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

const routes: Routes = [];

@NgModule({
    imports: [RouterModule.forRoot(routes)],
    exports: [RouterModule]
})
export class AppRoutingModule { }
Here, we have a constant named routes, it is simply an array of objects where each object has a type of Routes and represents a route. Each route will have some properties like below:
  • path: specifies the URL segment or pattern associated with the route. It can be a static path like 'home', a parameterized path like 'product/:id', or a wildcard path like '*' for handling undefined routes.
  • redirectTo: used to redirect a route to another one when the path matches instead of rendering a component.
  • pathMatch: used in conjunction with redirectTo to define how the router should match and redirect routes. It can have two possible values: 'prefix' or 'full'. Default is 'prefix'.
  • prefix: Angular will check the path from the left to see if the URL matches any given paths and stop when there is a match.
  • full: Angular will check the entire URL with given paths. It is essential to do this when the we define an empty path, because empty string is a prefix of any URL.
  • component: define which component will be loaded when the path matches.
  • outlet: tell the name of a RouterOutlet where Angular can place our component into when the path matches.
  • canActivate, canActivateChild, canDeactivate, resolve: an array of services which are used to attach route guards to specific routes or child routes. Route guards provide a way to control access to routes based on certain conditions. The canActivate guard determines if a route can be activated, canActivateChild is used for child routes, canDeactivate determines if a user can leave a route, and resolve is used to prefetch data before activating a route.
  • data: Additional data developer want to provide to the component. Default, no data is passed.
  • children: define an array of route objects that represent the child routes within a parent route.
  • loadChildren: used to specify a module that need to be lazy loaded.
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: 'home', component: HomeComponent },
    { path: 'about', component: AboutComponent },
];

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

RouterOutlet

    RouterOutlet is a directive exported by RouterModule. It is used to mark a place where the Angular router will render the component associated with the path we are at. We can have multiple outlets in our Angular application. The unnamed outlet will be the primary one, all the remaining outlets must have a name.

<router-outlet></router-outlet>
<router-outlet name="secondaryOutlet"></router-outlet>
As mentioned above, we can specify the router outlet where we want to render the component using the outlet property in the Route object and passing the name of the outlet you want to use.

RouterLink

    When we work with html, we often do a navigation by passing the url to the href attribute of the a tag.

navbar.component.html
<a href="/home">Home</a>
<a href="/about">About</a>
    But in Angular application, we barely use it because with this kind of navigation, every time we click on a link, all the resource of the page is downloaded and the entire application is reinitialized. With a huge project where we have so many code and logic, the cost of startup is higher, and reloading the whole application is what we want to avoid. To fix this problem, instead of using href attribute, we are going to use the routerLink directive. We can directly pass a string into routerLink or use property binding to bind to some non-string data.
navbar.component.html
<a routerLink="/home">Home</a>
<a [routerLink]="['/about']">About</a>
With routerLink, when we navigate, only the content of the target page is downloaded, not the whole application.
We can also navigate programmatically from our Typescript code by accessing to Angular router by injecting it to our component:
navbar.component.html
<button (click)="routeToHomePage()"></button>
navbar.component.ts
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';

@Component({
  selector: 'app-navbar',
  templateUrl: './navbar.component.html',
})
export class NavbarComponent implements OnInit {

  constructor(private router: Router) { }

  ngOnInit(): void {
  }

  routeToHomePage() {
    this.router.navigate(['/home'])
  }
}

Route guard
    Imagine that you want to verify if a user is logged in and has a right to access to a page, or to warn users when they are about to leave a route on which they are doing some modifications without saving anything. You can do that manually by adding the logic in the ngOnInit of your components, but it would be very cumbersome because you have to add it over and over again in every component you want to verify. Instead, we can use a feature built into the Angular router, running some logic before a route is loaded or once you leave a route, and it is router guard.
There are different types of route guards available in Angular
  1. CanActivate: This guard determines if a route can be activated or not. It is used to check if a user has the necessary permissions or authentication to access a specific route. If the guard returns true, the navigation is allowed, but if it returns false or a Promise/Observable that resolves to false, the navigation is blocked.
  2. auth.guard.ts
    import { Injectable } from '@angular/core';
    import { 
      CanActivate, 
      ActivatedRouteSnapshot, 
      RouterStateSnapshot, 
      UrlTree, 
      Router 
    } from '@angular/router';
    import { AuthService } from './auth.service';
    
    @Injectable({
      providedIn: 'root'
    })
    export class AuthGuard implements CanActivate {
      constructor(private authService: AuthService, private router: Router) {}
    
      canActivate(
        next: ActivatedRouteSnapshot,
        state: RouterStateSnapshot): boolean {
        if (this.authService.isAuthenticated()) {
          return true;
        } else {
          // Redirect to login page if not authenticated
          this.router.navigate(['/login']);
        }
      }
    }
    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';
    import { LoginComponent } from './login/login-component';
    import { AuthGuard } from './auth/auth.guard'
    
    const routes: Routes = [
        { path: 'home', component: HomeComponent, canActivate: [AuthGuard]},
        { path: 'about', component: AboutComponent },
        { path: 'login', component: LoginComponent },
    ];
    
    @NgModule({
        imports: [RouterModule.forRoot(routes)],
        exports: [RouterModule]
    })
    export class AppRoutingModule { }
  3. CanActivateChild: This guard is similar to CanActivate but specifically applies to child routes. It is used to control access to child routes of a parent route.
  4. CanDeactivate: This guard is used to determine if a user can leave a route or component. It is commonly used to prompt the user with a confirmation dialog when they try to navigate away from a form or unsaved changes.