Angular Functional Route Guards
June 20, 2023
On this page we will learn to use functional route guards in our Angular router application.
1. Angular route guards prevent unauthorized access. The route guards prevent users to navigate to parts of application without authorization.
2. The route guards are
canActivate
, canActivateChild
, canDeactivate
, canMatch
and resolve
. These are properties of Route
interface.
3. Angular 15 supports functional route guards to reduce boilerplate codes. In Angular 15, the interfaces that are implemented to be a guard have been deprecated. These deprecated interfaces are
CanActivate
, CanActivateChild
, CanDeactivate
, CanLoad
, CanMatch
and Resolve
.
4. Find the code snippet to use functional route guards
path: '', component: DashboardLayoutComponent, canActivate: [ (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => inject(AuthGuardService).canActivate(route, state) ]
canActivate
has been used as functional guard.
5. In functional route guards, we can call
inject
to get any required dependencies.
Now find some examples of functional route guards.
1. canActivate and canActivateChild
ThecanActivate
determines if the current user is allowed to activate the component. By default, any user can activate. The canActivate
accepts Array<CanActivateFn>
type data.
The
canActivateChild
determines if the current user is allowed to activate a child of the component. By default, any user can activate a child. It accepts Array<CanActivateChildFn>
type data.
Find the code to use them as function.
app-routing.module.ts
const routes: Routes = [ { path: '', component: DashboardLayoutComponent, canActivate: [ (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => inject(AuthGuardService).canActivate(route, state) ], children: [ { path: 'article', loadChildren: () => import('./article/article.module').then(m => m.ArticleModule) }, { path: 'address', component: AddressComponent } ] }, ------ ];
inject
is called to get any required dependencies.
article-routing.module.ts
const articleRoutes: Routes = [ { path: '', component: ArticleComponent, children: [ { path: 'list', component: ArticleListComponent, canActivateChild: [ (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => inject(AuthGuardService).canActivateChild(route, state) ], children: [ { path: ':id', component: ArticleEditComponent } ] } ] } ];
import { Injectable } from '@angular/core'; import { Router, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; import { AuthService } from './auth.service'; @Injectable() export class AuthGuardService { constructor(private authService: AuthService, private router: Router) { } canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { let url: string = state.url; console.log('Url:' + url); if (this.authService.isUserLoggedIn()) { return true; } this.authService.setRedirectUrl(url); this.router.navigate([this.authService.getLoginUrl()]); return false; } canActivateChild(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { let loggedInUser = this.authService.getLoggedInUser(); if (loggedInUser.role === 'ADMIN') { return true; } else { console.log('Unauthorized to open link: ' + state.url); return false; } } }
2. canDeactivate
ThecanDeactivate
determines if the current user is allowed to deactivate the component. By default, any user can deactivate. It accepts Array<CanDeactivateFn<any>>
type data.
Find the code to use it as function.
country-routing.module.ts
const countryRoutes: Routes = [ { path: 'country', component: CountryComponent, children: [ { path: 'add', component: AddCountryComponent, canDeactivate: [ (component: CanComponentDeactivate, state: RouterStateSnapshot) => inject(CanDeactivateGuard).canDeactivate(component, state) ] }, { path: 'list', component: CountryListComponent, children: [ { path: 'edit/:country-id', component: CountryEditComponent, canDeactivate: [ (component: CountryEditComponent, state: RouterStateSnapshot) => inject(CountryEditCanDeactivateGuard).canDeactivate(component, state) ] } ] } ] } ];
import { Injectable } from '@angular/core'; import { RouterStateSnapshot } from '@angular/router'; import { Observable } from 'rxjs'; export interface CanComponentDeactivate { canDeactivate: () => Observable<boolean> | Promise<boolean> | boolean; } @Injectable() export class CanDeactivateGuard { canDeactivate(component: CanComponentDeactivate, state: RouterStateSnapshot) { let url: string = state.url; console.log('Url: ' + url); return component.canDeactivate ? component.canDeactivate() : true; } }
import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; import { RouterStateSnapshot } from '@angular/router'; import { CountryEditComponent } from './country/country-list/edit/country.edit.component'; import { DialogService } from './dialog.service'; @Injectable() export class CountryEditCanDeactivateGuard { constructor(private dialogService: DialogService) { } canDeactivate(component: CountryEditComponent, state: RouterStateSnapshot): Observable<boolean> | boolean { let url: string = state.url; console.log('Url: ' + url); if (!component.isUpdating && component.countryForm.dirty) { component.isUpdating = false; return this.dialogService.confirm('Discard changes for Country?'); } return true; } }
3. resolve
Theresolve
is a map of DI tokens used to look up data resolvers. It accepts ResolveData
type data.
Find the code to use it as function.
person-routing.module.ts
{ path: 'personList', component: PersonListComponent, children: [ { path: 'detail/:person-id', component: PersonDetailComponent, resolve: { personDetail: (route: ActivatedRouteSnapshot) => inject(PersonDetailResolver).resolve(route) } } ] }
import { Injectable } from '@angular/core'; import { Router, ActivatedRouteSnapshot } from '@angular/router'; import { PersonService } from './services/person.service'; import { Person } from './person'; @Injectable() export class PersonDetailResolver { constructor(private personService: PersonService, private router: Router) { } resolve(route: ActivatedRouteSnapshot): Promise<Person | null> { let id = route.paramMap.get('person-id') ?? ''; console.log('Resolving for person id:' + id); return this.personService.getPerson(id).then(person => { if (person) { return person; } else { this.router.navigate(['/person/personList']); return null; } }); } }