Angular Functional Route Guards

By Arvind Rai, 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)
] 
Here 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

The canActivate 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
			}
		]
	},
    ------
]; 
The 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
					}
				]
			}
		]
	}
]; 
auth-guard.service.ts
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

The canDeactivate 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)
                        ]
                    }
                ]
            }
        ]
    }
]; 
can-deactivate-guard.service.ts
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;
  }
} 
app/country-edit-can-deactivate-guard.service.ts
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

The resolve 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)
			}
		}
	]
} 
person-detail.resolver.ts
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;
      }
    });
  }
} 

Reference

Download Source Code

POSTED BY
ARVIND RAI
ARVIND RAI
LEARN MORE








©2024 concretepage.com | Privacy Policy | Contact Us