Angular routing

Angular routing is a mechanism that enables navigation between different views or pages of an Angular application. In this post, I will explain the basic concepts of angular routing.

Setup basic angular routing

  • Define the app.routing.ts file
TypeScript
import { Routes } from "@angular/router";
import { AboutComponent } from "./about/about.component";
import { ContactComponent } from "./contact/contact.component";
import { HomeComponent } from "./home/home.component";

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

Here are all your routes. When someone tries to pen www.your-application/contact– the ContactComponent will load, and if the address is www.your-application/aboutAboutComponent. Home and empty route are reserved for the HomeComponent.

The Routes type represents an array of route configuration objects. Each object in the array defines a single route in your application and maps a URL path to a component.

The RouterModule is a module that provides the necessary services and directives for routing in your application. It contains the Router service, which is responsible for managing the navigation state of your application, as well as a set of directives such as router-outlet and routerLink that are used to bind the Router to your templates.

  • Configure the RouterModule module:

Register the RouterModule in the imports array of your module:

TypeScript
@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    RouterModule,
    RouterModule.forRoot(appRoutes)
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

The actual setup of your routes is done by calling RouterModule.forRoot(appRoutes). It sets up the Router to work and understand the routes you provided.

  • Add the router-outlet directive to the template where you want to display the content of the currently selected route:

Add this in the app.component.html:

TypeScript
<nav class="navbar navbar-expand-lg navbar-light bg-light">
  <div class="navbar">
    <ul class="navbar-nav mr-auto">
      <li class="nav-item">
        <a class="nav-link" routerLink="/home">Home</a>
      </li>
      <li class="nav-item">
        <a class="nav-link" routerLink="/contact">Contact</a>
      </li>
      <li class="nav-item">
        <a class="nav-link" routerLink="/about">About</a>
      </li>
    </ul>
  </div>
</nav>

<router-outlet></router-outlet>

The <router-outlet> directive is a placeholder used to display the content of the currently selected route. When a user navigates to a different route, the Router replaces the contents of the <router-outlet> with the component that is associated with that route.

Use the routerLink directive in your template to navigate to different routes. For instance – <a class="nav-link" routerLink="/home">Home

Setup dynamic angular routing

Sometimes your routes may accept parameters at runtime. These routes are called dynamic.

  • Let’s define a product list component and a model Product:
TypeScript
import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-product-list',
  templateUrl: './product-list.component.html'
})
export class ProductListComponent implements OnInit {
  public products: Product[] = [];

  constructor() { }

  ngOnInit() {
    this.products = [
      new Product("pizza", 3),
      new Product("burger", 4),
      new Product("sallad", 5)
    ]
  }
}

export class Product {
  name: string = '';
  id: number = 0;

  constructor(name: string, id: number) {
    this.name = name;
    this.id = id;
  }
}
  • In the template generate dynamic routes, for each product:
TypeScript
<a *ngFor="let product of products" [routerLink]="['/products', product.id]">
    {{ product.name }}
</a>

The routeLink directive will build a route in the format product/{id} for each product.

  • Define a product component:
TypeScript
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

@Component({
  selector: 'app-product',
  templateUrl: './product.component.html'
})
export class ProductComponent implements OnInit {
  public productId: number = 0;

  constructor(private route: ActivatedRoute) { }

  ngOnInit() {
    this.route.params.subscribe(params => {
      this.productId = params['id'];
      // Get prouct from server, based on the ID
    });
  }
}

Use the ActivatedRoute to parse the dynamic id parameter from the route. With this id, you can request a backend service to fetch the product. ActivatedRoute can be used to access the current route’s parameters, query parameters, and data. It is typically used in components that need to display information based on the current route.

  • Product component template:
TypeScript
<p>Fetching product with id {{productId}}</p>
  • Register the product dynamic route, and the product list:
TypeScript
export const appRoutes: Routes = [
    { path: '', component: HomeComponent },
    { path: 'home', component: HomeComponent },
    { path: 'contact', component: ContactComponent },
    { path: 'about', component: AboutComponent },
    { path: 'products', component: ProductListComponent },
    { path: 'products/:id', component: ProductComponent },
];

Now in the browser you can see the 3 generated links:

Products list component
Products list component

And if you click on pizza for example, the id will be passed to the product component route, and parsed by the ActivatedRoute:

Dynamic angular routing
Dynamic angular routing

Create child route

The route from the previous example product/:id may be called child route of the /products route. Child routes in Angular are routes that are nested within a parent route. In our example the dynamic :id route can be easily nested inside the products one. And this is the better approach instead of declaring them separately.

To define child routes, you need to include an additional array of route configurations within the parent route configuration object. Here’s an example:

TypeScript
export const appRoutes: Routes = [
    { path: '', component: HomeComponent },
    { path: 'home', component: HomeComponent },
    { path: 'contact', component: ContactComponent },
    { path: 'about', component: AboutComponent },
    {
        path: 'products',
        children: [
            { path: '',  component: ProductListComponent },
            { path: ':id',  component: ProductComponent }
        ]
    }
];

The products route is the parent route, and if it is accessed without any parameters it will load the ProductListComponent. If you add an id, the ProductComponent will be loaded.

Working with the resolve property

The resolve property in Angular is a way to pre-fetch data for a route before the component associated with that route is displayed. With the resolve property, you can define a service or a function that returns data that is required for the component, and the Router will wait for the data to be loaded before navigating to the component.

Here is how to tell your component, it needs a resolver:

TypeScript
{
    path: 'products',
    children: [
        { path: '', component: ProductListComponent },
        {
            path: ':id',
            component: ProductComponent,
            resolve: {
                user: UserResolver
            }
        }
    ]
}

The UseResolver will be called just before the component loads. Here is its definition:

TypeScript
@Injectable()
export class UserResolver implements Resolve<User> {
    constructor(private userService: UserService) { }

    resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<User> {
        // Get the currentUserId on login and save it in a store
        // Here it is hard coded for the simplicity of the example
        const currentUserId: number = 5;
        return this.userService.getUser(currentUserId);
    }
}

Here is the UserService with the hard coded result:

TypeScript
@Injectable()
export class UserService {

    constructor() { }

    getUser(userId: number): Observable<User> {
        // Get the user from server, based on the ID
        return of(new User("Alex", userId, true));
    }
} 

Also, don’t forget to register the UserResolver and the UserService in the providers array of the app.module.ts, like this providers: [UserService, UserResolver].

And now, you can access the resolved data in your component using the ActivatedRoute service:

TypeScript
ngOnInit() {
    this.route.params.subscribe(params => {
      this.productId = params['id'];
      // Get prouct from server, based on the ID
    });

    this.user = this.route.snapshot.data['user'];
    console.log(this.user);
  }

And when you access some product you will get the user logged in the console:

Angular routing resolver demo
Angular routing resolver demo

Using canActivate

The canActivate property in Angular is used to control access to a route. It allows you to specify a guard function that determines whether a user is allowed to activate a particular route, based on certain criteria. If the guard function returns true, the user is allowed to navigate to the route, and if it returns false, the navigation is blocked.

Here’s an example of how to use the canActivate property in a route:

TypeScript
{
    path: 'admin-panel',
    component: AdminPanelComponent,
    canActivate: [AuthGuard]
}

The AuthGuard will be called just before loading the component. Here you an place the logic for component’s persmissions:

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

    canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
        if (this.authService.isAdmin()) {
            return true;
        } else {
            console.log('The current user doesn\'t have admin rights');
            this.router.navigate(['']);
            return false;
        }
    }
}

The AuthService just returns false in this example:

TypeScript
@Injectable()
export class AuthService {

    constructor(private userService: UserService) { }

    isAdmin(): boolean {
        // Check if the current user is admin and return true or false
        return false;
    }
} 

Register the AuthGuard and the AuthService in the providers array of the app.module.ts.

When you try to access the admin-panel route, angular will redirect to the empty '' route and will log the guard message in the console:

Angular routing guard demo
Angular routing guard demo

Download demo