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.
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 map
, catchError
, of 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 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