Multiple nested guards on one route

angular multiple nested guards on one route
Difficulty

We may need multiple guards on one route in some situations. It may happen that sometimes a shared guard is needed between several components which however require further and different verifications.
A possible solution in those cases may be to insert “matryoshka” Guards, making the focus of the work on one side and further checks on the other.

In today’s recipe we need:

  • 2 eggs… uh, Guards
  • (optional) 1 or more Services
  • 1 Routing Module

The guards

We start by creating the first Guard, the one that will be nested in the others and that may not be called directly:

// first.guard.ts
import { Injectable } from "@angular/core";
import {
  CanActivate,
  ActivatedRouteSnapshot,
  RouterStateSnapshot,
  UrlTree,
  Router
} from "@angular/router";
// import { Observable } from "rxjs";
import { FakeService } from "./fake.service";

@Injectable({
  providedIn: "root"
})
export class FirstGuard implements CanActivate {
  constructor(private _router: Router, private _fakeService: FakeService) {}

  canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): Promise<boolean> {
    console.log("First (nested) guard checking...");
    return new Promise<boolean>((resolve, reject) => {
      this._fakeService.getOk().subscribe({ // call a service to verify user...
        next: resOk => {
          console.log("...first (nested) guard success!");
          resolve(true); // return the successfull result of the service.
        },
        error: err => {
          console.log("...first (nested) guard failed!");
          resolve(false); // return the fail result of the service.
        }
      });
    });
  }
}

Then we create the second Guard which calls the first one inside it and which can perform further operations necessary to verify the user:

// second.guard.ts
import { Injectable, OnDestroy } from "@angular/core";
import {
  CanActivate,
  ActivatedRouteSnapshot,
  RouterStateSnapshot,
  UrlTree,
  Router,
  Params
} from "@angular/router";
import { forkJoin, Observable, of, Subscription } from "rxjs";
import { catchError } from "rxjs/operators";
import { FirstGuard } from "./first.guard";

@Injectable({
  providedIn: "root"
})
export class SecondGuard implements CanActivate {
  constructor(private _router: Router, private _firstGuard: FirstGuard) {}

  canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): Observable<boolean | UrlTree> | Promise<boolean | UrlTree>
    | boolean | UrlTree {
    console.log("Second guard checking...");
    // call the first guard method and verify the result
    return this._firstGuard.canActivate(next, state).then((auth: boolean) => {
      if (auth) {
        console.log("...second guard success!");
        // ...other interesting implementations
        return true;
      } else {
        console.log("...second guard failed!");
        // ...other interesting implementations
        this._router.navigate(["fail"]);
        return false;
      }
    });
  }
}

In the example we have called a service that can serve as a check for the first guard.
Here is how we structured it, even if indifferent to implementation purposes:

// fake.service.ts
import { Injectable } from "@angular/core";
import { Observable } from "rxjs";
import { map } from "rxjs/operators";
import { HttpClient } from "@angular/common/http";

@Injectable()
export class FakeService {
  constructor(private _http: HttpClient) {}

  public getOk(): Observable<any[]> {
    console.log("service getOk");
    return this._http
      .get<any>("https://jsonplaceholder.typicode.com/todos")
      .pipe(
        map(response =>
          response.map(res => {
            return {
              id: res.id,
              completed: res.completed
            };
          })
        )
      );
  }
}

Calling multiple guards in a route

Finally, in order for the implementation to take place, we need the place to verify. We will put it inside in our routing module:

// app-routing.module.ts
import { NgModule } from "@angular/core";
import { Routes, RouterModule } from "@angular/router";
import { CommonModule } from "@angular/common";
import { AppComponent } from "./app.component";
import { SecondGuard } from "./second.guard";
import { HelloComponent } from "./hello.component";
import { FailComponent } from "./fail.component";

const routes: Routes = [
  {
    path: "",
    component: HelloComponent,
    pathMatch: "full",
    canActivate: [SecondGuard]
  },
  {
    path: "fail",
    component: FailComponent
  }
];

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

The important part can be found in the canActivate parameter, which activates the Guard and with it the verification cycle starts.

We also insert the right imports inside the main module to make services and routing work correctly.
I specify that the 2 components:

  • HelloComponent is the component submitted to the Guards which will be accessible only in case of positive outcome.
  • FailComponent is the component called in case of failure of one of the controls.
// app.module.ts
import { NgModule } from "@angular/core";
import { BrowserModule } from "@angular/platform-browser";

import { AppComponent } from "./app.component";
import { HelloComponent } from "./hello.component";
import { FakeService } from "./fake.service";
import { AppRoutingModule } from "./app-routing.module";
import { RouterModule } from "@angular/router";
import { HttpClientModule } from "@angular/common/http";
import { FailComponent } from "./fail.component";

@NgModule({
  imports: [
    BrowserModule,
    RouterModule.forRoot([]),
    AppRoutingModule, // to use routing
    HttpClientModule // to use services
  ],
  declarations: [AppComponent, HelloComponent, FailComponent],
  bootstrap: [AppComponent],
  providers: [FakeService]
})
export class AppModule {}

An example of multiple guards on one route

Below is an example of what we have seen:

That’s all.
Try it at home!

2
2 people like this.
Please wait...

Leave a Reply

Thanks for choosing to leave a comment.
Please keep in mind that all comments are moderated according to our comment policy, and your email address will NOT be published.
Please do NOT use keywords in the name field. Let's have a personal and meaningful conversation.