Guards and Resolvers (in Angular)

Angular is one of many web frameworks that make web development easier by providing you with the tools to make the development process cleaner and quicker. Out of all the mobile and web development frameworks I’ve used till now – native Android, native iOS, plain web, Angular and React (which is not that many), Angular is my favourite framework so far.

Did you know?
The first-ever website that was hosted on the internet was back in August 6, 1991? This website was developed and hosted by Tim Berners-Lee, who intuitively is also the one who developed the foundation of the Hyper Text Markup Language (HTML). It might not seem like a long time, but compared to the time when “real” mobile apps came up along with the App Store in June 2007, which is about 14 years ago (still pretty old), websites still have the seniority of age.

That being said, in this post I’m going to be talking about 2 of its features that are greatly useful in controlling navigation within the app. In this post, I’m going to use Angular on top of the Ionic framework which is used to created cross-platform apps. So without further ado, let’s get right into it.

To begin with, let me explain what guards and resolvers are in Angular in the first place.

Guards

A guard in Angular is a class with a method that decides whether a route can be loaded, activated or even deactivated.

Auth guards can be compared to the Queen’s guards who block people from entering into the palace unless they are authorized.

A router guard is used to prevent users from navigating to parts of the application without authorization. The following interfaces are guards that decide whether to perform the navigation:

Resolvers

A resolver in Angular is a class with a method that acts as a data provider for the page’s initialization and makes the router wait for the data to be resolved before the route is finally activated.

Resolvers can be thought of as a coordinator that makes the meeting wait for the next speaker to get ready before handing over the mic to them.

A point to be noted is that resolvers are also route guards themselves according to Angular’s definition, but I wanted to introduce it separately since they possess a different kind of behavior when compared to the other route guards. Resolvers do not decide whether to perform the navigation or not, but instead only wait for a asynchronous/synchronous operation to be performed and results returned before navigating to the page.

The interface that is used to implement a resolver is:

With this we’ve seen what guards and resolvers are, and what they do when added to a route. What we need to know though is what purpose either of them have in an application, and how to implement them in your own web app.

Why do we need them?

As we’ve already seen, guards protect routes from being navigated into or out of based on the logic that we provide. The most common use for them is to disallow unauthenticated users to visit authenticated pages. If the user does try to visit the route directly or by clicking on link that navigates to that route, the guard could display an error message or redirect the user to the login page so that he/she can be authenticated first.

On the other hand, we’ve seen that resolvers make the app wait for an asynchronous/synchronous operation before the route is navigated into. Resolvers are mostly used to fetch data from a backend server that needs to be used in the page bound to the route. If we don’t use a resolver in this case, the page may render with a state that is not consistent with the backend data that is being fetched. That being said, this might be only for a few seconds since the page might get re-rendered once the data has finished loading. Alternatively, in addition to backend requests, this could even be one time subscriptions to events that are triggered by the application somewhere else.

How do we implement them?

Both guards and resolvers are implemented the same way, that is by creating a class that implements the interface needed (i.e. CanActivate, CanLoad or Resolve) which is then added to the route that needs to have the guard/resolver implemented.

Let us take 2 examples for this post. A guard and a resolver.

Guard

Create the guard class that implements the guard interface, say CanActivate.

is-authenticated.guard.ts

import { Injectable } from '@angular/core';
import {
  CanActivate,
  ActivatedRouteSnapshot,
  RouterStateSnapshot,
  Router,
} from '@angular/router';
/**
 * The route guard where the user's permissions are checked before entering an authenticated view.
 *
 * The route is allowed to be entered when the `canActivate` method returns `true`, and not allowed when it returns `false`.
 */
@Injectable()
export class IsAuthenticatedGuard implements CanActivate {
  constructor(private router: Router) {}
  /**
   * Run before a view using this route guard is entered.
   * @param next Contains the information about a route associated with a component loaded in an outlet at a particular moment in time.
   * @param state Represents the state of the router at a moment in time.
   * @returns Whether or not the view is allowed to be entered.
   */
  async canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): Promise<boolean> {
    return new Promise<boolean>((resolve) => {
      const unsubscribe = firebase.auth().onAuthStateChanged(
        (user) => {
          unsubscribe();
          if (user) {
            console.log(IsAuthenticatedGuard.name, 'User is signed in');
            return resolve(true);
          } else {
            console.log(IsAuthenticatedGuard.name, 'No user is signed in');
            this.router.navigateByUrl('generic-login');
            return resolve(false);
          }
        },
        (err) => {
          console.log(IsAuthenticatedGuard.name, 'Error occurred', err);
          this.router.navigateByUrl('generic-login');
          return resolve(false);
        }
      );
    });
  }
}

The above code is the auth guard for an app that uses Firebase Authentication, and therefore we can only use the methods that are provided by Firebase Auth to know if the user is authenticated. In this case, we make a one-time subscription to the firebase.auth().onAuthStateChanged observable which always emits one event first irrespective of whether there was a state change. In this case since we implement the CanActivate interface, we also implement its canActivate method which returns a boolean (either synchronously, as a Promise, or as an Observable). Again in the above case it is a Promise.

Now first add this guard as a provider to a module that you want to use it in. If you want this guard to be used throughout the application, you could add it in a SharedModule that you import in all your modules. I am going to do just that.

import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';
import { IonicModule } from '@ionic/angular';
import { IsAuthenticatedGuard } from './guards/is-authenticated/is-authenticated.guard';
@NgModule({
  providers: [IsAuthenticatedGuard],
  imports: [
    CommonModule,
    IonicModule.forRoot(),
    RouterModule,
    FormsModule,
  ],
  exports: [
    IonicModule,
    CommonModule,
    RouterModule,
    FormsModule,  ],
})
export class SharedModule { }

Once the guard has been added to a module, the next step is to add it to a route. I am going to add this to the /home route of the app.

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { IsAuthenticatedGuard } from '../shared/guards/is-authenticated/is-authenticated.guard';
import { HomePageComponent } from './home-page/home-page.component';
const routes: Routes = [
  {
    path: '',
    component: HomePageComponent,
    canActivate: [IsAuthenticatedGuard]
  }
];
@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule],
})
export class HomeRoutingModule {}

Here we add the IsAuthenticatedGuard to the canActivate field of the route. So now whenever we navigate to the /home route, the auth guard gets triggered, which does the necessary next steps.

Pretty simple right? Alright, let’s take an example of a resolver now.

Resolver

Similar to a guard, we create a resolver class that implements the Resolve interface.

detail-page.resolver.ts

import { Injectable } from '@angular/core';
import {
  ActivatedRouteSnapshot,
  RouterStateSnapshot,
  Router,
  Resolve,
} from '@angular/router';
import { VideoService } from '../../videos/services/videos.service';
/**
 * A resolver that waits for the video details to be fetched before loading the route.
 */
@Injectable()
export class DetailPageResolver implements Resolve {
  constructor(
    private router: Router,
    private videoService: VideoService) {}
  /**
   * Run before a view that is using this resolver is entered.
   * @param next Contains the information about a route associated with a component loaded in an outlet at a particular moment in time.
   * @param state Represents the state of the router at a moment in time.
   * @returns Video details corresponding to the given id.
   */
  async resolve(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): Promise<any> {
    const videoId = next.paramMap.get('id');
    try {
      const video = await this.videoService.getVideoDetails(videoId);
      return video;
    } catch (err) {
      this.router.navigateByUrl('error-404');
      return;
    }
  }
}

The above code basically implements the resolver for a video detail page, which has a parameter in its URL, which we use to fetch the video details. Here we use a VideoService that makes an HTTP request to some backend server that hosts the details of all the videos.

You might ask why we are not using ngOnInit or any other lifecycle hook to fetch this data, and instead of using a resolver. This is because Angular lifecycle hooks are bound to the page, and are only triggered once the component has already started navigation to the page. Here is a flow chart that describes the different lifecycle hooks in Ionic:

The resolver here is triggered before ngOnInit, so we can be sure that any asynchronous operations are completely done even before the page has been navigated into.

Alright, now that we have our resolver ready, let’s add it to a route.

videos-routing.module.ts

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { DetailPageResolver } from './detail-page/resolvers/detail-page.resolver';
import { VideoDetailPageComponent } from './detail-page/detail-page.component';
const routes: Routes = [
  {
    path: '',
    component: VideoDetailPageComponent,
    resolve: {
      videoDetails: DetailPageResolver,
    }
  }
];
@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule]
})
export class VideosRoutingModule { }

Here I’ve added the resolver to the resolve property of the route. In this case, I’ve chosen to also make the response from the resolver available on the page, so I’ve mapped it to the videoDetails variable. Therefore now I can get the video details from the route params through ActivatedRoute.

detail-page.component.ts

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
@Component({
  selector: 'app-video-detail',
  templateUrl: './detail-page.component.html',
  styleUrls: ['./detail-page.component.scss'],
})
export class VideoDetailPageComponent implements OnInit {
  constructor(private route: ActivatedRoute) { }
  ngOnInit() {
    this.route.data.toPromise().then((videoDetails: any) => {
      console.log(videoDetails);
      // Perform operations using videoDetails
    });
  }
}

And that’s all. We have now implemented an auth guard as well as a resolver. If you’ve found this content useful make sure to leave a like on this post, and write a comment on what topic you would like to see a post on next.

Until next time. Adios!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.