Sunday, June 14, 2020

Angular 9|8|7 Auth Guard CanActivate, CanDeactivate and Resolve in Angular Routing Quick Example


Angular routing plays an important role in a real-world application which is responsible for loading different components which may have sensitive data as well. Angular provides a full proof mechanism to develop an application which can add a protecting shield for accessing components.

Angular Auth Guards.

Primis Player Placeholder

Angular Auth Guards add authentication layer which helps in preventing loading of components in various scenarios like Login authentication, permission-based authentication etc. We can add checks before loading components using Angular guards which are of four types

CanActivate: Checks route navigation before the component is loaded.

CanActivateChild: Checks route children navigation before the component is loaded.

CanDeactivate: Checks navigation from the current route eg leaving the partially filled form.

Resolve: Resolve loads/ retrieves data before the route is activated.

CanLoad: Checks to see if a user can route to a module that lazy-loaded.

Here we will discuss only CanActivate and Resolve Guards. For demonstration, we will create a sample application in Angular’s latest version 9 having IMDB API service to fetch scary movies series 😛

Let’s get started!

Make sure you have the latest version on Angular CLI installed

$ npm install -g @angular/cli

 

Create a new Angular project with Routing enabled and Styling selected to CSS

$ ng new NG9Guards
$ cd NG9Guards

Create new pages and services

We will create Home, Details pages with new Guard services for CanActivate and Resolve. Run following cmd in ng CLI to create pages and services

$ ng generate component home --spec false
$ ng generate component details --spec false
$ ng generate service fooResolve --spec false
$ ng generate service fooGuard --spec false

Added --spec false to not create spec files by default

Application Routing

In the app-routing.module.ts file, we have Routes for Home and Details pages.

Modify Route path for details to add the parameter 'imbdid' which will be passed go get movie details using HTTP get calls

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

 

CanActivate service

FooGuard service will implement CanActivate interface to act as CanActivate Guard which we will add in Routes. Replace below code in the foo-guard.service.ts file

Here we are calling IMDB free API using Angular's HttpClient get method. In the canActivate method, we are returning an Observable then handling response using Rxjs mapcatchErrorof methods.

canActivate return true or false according to login we add in it, which can be a login authentication. Here we are just checking if the response is error or movie details. Based or response returning boolean. We canActivate get false, details route will not be activated and if true details route will navigate successfully.

import { Injectable } from '@angular/core';
import { CanActivate } from '@angular/router';
import { ActivatedRouteSnapshot } from '@angular/router';
import { Observable, of } from 'rxjs';
import { HttpClient } from '@angular/common/http';
import { map, catchError } from 'rxjs/operators';

const APIKEY = "e8067b53";

@Injectable({
  providedIn: 'root'
})
export class FooGuardService implements CanActivate {


  constructor(
    private httpClient: HttpClient
  ) { }

  canActivate(route: ActivatedRouteSnapshot): Observable<boolean> {
    console.log("guard!");
    let imbdid = route.paramMap.get('imbdid');
    return this.httpClient.get('http://www.omdbapi.com/?i=' + imbdid + '&apikey=' + APIKEY).pipe(
      map(res => {
        if (res['Error']) {
          alert("Movie not found at guard!");
          return false;
        } else {
          return true;
        }
      }),
      catchError((err) => {
        return of(false);
      })
    );

  }


}

 

Similarly, in Foo Resolve service we will implement Resolve interface then add a resolve method. Replace below code in the foo-resolve.service.ts file.

In this service, we have resolve method which is simply getting movie details using a get call.

Resolve is basically used to retrieve data which may be from remote servers. The route navigates to the page only after the resolve method is finished its operation.

import { Injectable } from '@angular/core';
import { Resolve, ActivatedRouteSnapshot } from '@angular/router';
import { HttpClient } from '@angular/common/http';

const APIKEY = "e8067b53"; 

@Injectable({
  providedIn: 'root'
})
export class FooResolveService implements Resolve<any>{

  constructor(
    private httpClient:HttpClient
  ) { }

  resolve(route: ActivatedRouteSnapshot){
    let imbdid = route.paramMap.get('imbdid');

    return this.httpClient.get('http://www.omdbapi.com/?i='+imbdid+'&apikey='+APIKEY);
  }
}

 

Add Resolve and CanActivate Guards in Routes

In the app-routes.module.ts file, we will modify details route to add canActivate and resolve parameters.

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { DetailsComponent } from './details/details.component';
import { FooResolveService } from './foo-resolve.service';
import { FooGuardService } from './foo-guard.service';

const routes: Routes = [
  { path:'', redirectTo:'home',pathMatch:'full'},
  { path:'home', component: HomeComponent},
  { 
    path:'details/:imbdid', 
    component: DetailsComponent,
    canActivate:[FooGuardService],
    resolve:{
      movie:FooResolveService
    }
  }
];

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

canActivate can have any number of Guards in the array. resolve will take the response from the service's resolve method then pass to movie attribute.

Update Home and Details Components

In Home, we will have a list of movies on click on which details page with that movie details will open. Here we have "Scary Movie 5" which is not found at canActivate service and will return false to show an Alert message.

home.component.html

<div>

  <div class="card" style="width: 50%; margin: 30px auto">
    <div class="card-body">
      <h5 class="card-title">Movie Details</h5>
      <p class="card-text">Click on links below to get Movie details</p>
    </div>
    <ul class="list-group list-group-flush">
      <li class="list-group-item">
        <a [routerLink]="['/details/tt0175142']">Scary Movie</a>
      </li>
      <li class="list-group-item">
        <a [routerLink]="['/details/tt0257106']">Scary Movie 2</a>
      </li>
      <li class="list-group-item">
        <a [routerLink]="['/details/tt0306047']">Scary Movie 3</a>
      </li>
      <li class="list-group-item">
        <a [routerLink]="['/details/tt0362120']">Scary Movie 4</a>
      </li>
      <li class="list-group-item">
        <a [routerLink]="['/details/tt0362XXX']">Scary Movie 5</a>
      </li>
    </ul>
  </div>

</div>

In the home.component.ts file, there is nothing to update

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit {

  constructor() { }

  ngOnInit() {
  }

}

In Details, component we will get Route snapshot of "movie" which we passed in Routes resolve attribute to show details.

details.component.ts

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

@Component({
  selector: 'app-details',
  templateUrl: './details.component.html',
  styleUrls: ['./details.component.css']
})
export class DetailsComponent implements OnInit {

  movieDetails:any;

  constructor(
    private route:ActivatedRoute
  ) { }

  ngOnInit() {
    this.movieDetails = this.route.snapshot.data['movie'];
  }

}

details.component.html

<div>

  <div class="card" style="margin: 30px auto">

    <div class="card-body">

      <div class="row">
        <div class="col-md-4">
          <img src="{{movieDetails['Poster']}}" class="card-img-top" style="width: 300px;">
        </div>
        <div class="col-md-8">
          <ul class="nav">
            <li class="nav-item">
              <a class="nav-link" [routerLink]="['/home/']">
                <button type="button" class="btn btn-primary"><i class="glyphicon glyphicon-chevron-left"></i>Back</button>
              </a>
            </li>
          </ul>
          <h5 class="card-title">{{movieDetails['Title']}} ({{movieDetails['Year']}})</h5>
          <p class="card-text">{{movieDetails['Plot']}}</p>
          <p class="card-text"><strong>Genre</strong>: {{movieDetails['Genre']}}</p>
          <p class="card-text"><strong>IMDB Rating</strong>: {{movieDetails['imdbRating']}}</p>
          <p class="card-text"><strong>Released</strong>: {{movieDetails['Released']}}</p>
          <p class="card-text"><strong>Actors</strong>: {{movieDetails['Actors']}}</p>
        </div>
      </div>
    </div>

  </div>

That's it 🙂 now you can run your application in a browser by running

$ ng serve --open

Angular 9|8|7 Auth Guard CanActivate, CanDeactivate and Resolve in Angular Routing Quick Example

Angular Guards is a very important mechanism to understand as a web application can have multiple scenarios where authentication based access is required. We will discuss other Guards type in future posts.

 

No comments:

Post a Comment

Free hosting web sites and features -2024

  Interesting  summary about hosting and their offers. I still host my web site https://talash.azurewebsites.net with zero cost on Azure as ...