Angular CanActivateFn Example

By Arvind Rai, February 25, 2024
On this page we will learn to use CanActivateFn in our Angular application. Angular CanActivateFn provides functional approach to write code for canActivate route guard.
To perform canActivate route guard, we need to know following points.
1. CanActivateFn : Signature of a function to perform canActivate route guard.
2. canActivate : A property of Route interface that accepts an array of CanActivateFn instances.
3. mapToCanActivate : Maps injectable classes containing a method named as canActivate to an array of equivalent CanActivateFn.

1. CanActivateFn

CanActivateFn is a signature of a function that will be used by canActivate guard on a Route.
Find the CanActivateFn signature from Angular doc.
(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => 
Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree; 
Parameters :
route : ActivatedRouteSnapshot instance that contains informations about a route of the currently loaded component.
state : RouterStateSnapshot instance that represents the state of router at that time.

Returns :
CanActivateFn returns boolean, UrlTree or Observable.

1. canActivate is a property of Route interface. canActivate accepts an array of CanActivateFn to determine if the current user is allowed to activate the component.
2. If CanActivateFn returns true, navigation is allowed otherwise navigation is cancelled.
3. If CanActivateFn returns UrlTree, the current navigation is cancelled and new navigation begins.
4. If no CanActivateFn is provided to canActivate then by default any user can activate the route.

2. Using CanActivateFn

For the demo application, I am using canActivate guard with a route that will be activated only when user is logged in.
Step-1 : Create a function with CanActivateFn signature.
const loginCanActivate: CanActivateFn = (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => 
   inject(LoginService).canActivate(route, state); 
loginCanActivate is the instance of CanActivateFn.
Step-2 : canActivate is the property of Route. The canActivate is an array of CanActivateFn. So assign loginCanActivate to the route which needs to be guard.
export const ROUTES: Routes = [
    { 
      path: 'bookdetail',
      component: BookComponent,
      canActivate: [loginCanActivate]
    }
]; 
Step-3 : Our LoginService class can be defined as below.
@Injectable({
  providedIn: 'root'
})
export class LoginService {
  constructor(private router: Router) {
  }
  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
    if (this.isUserLoggedIn()) {
      return true;
    } else {
      this.router.navigate([this.getLoginUrl()]);
    }
    console.log(state.url);
    return false;
  }
  isUserLoggedIn() {
    return true;
  }
  getLoginUrl() {
    return "/login";
  }
} 
I have created canActivate() method with two parameters as ActivatedRouteSnapshot and RouterStateSnapshot. The return type of canActivate() method is boolean and if this method returns true, user will be able to visit that route and if return value is false, user will not be able to visit that route.
Our LoginService class contains a method with name canActivate() but it is not necessary to keep this name. We can use any name.
Find the sample code. Here I will create a method named as userCanActivate as below.
@Injectable({
  providedIn: 'root'
})
export class LoginService {
  userCanActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
  	------
    return true;
  }
} 
Call userCanActivate method to instantiate CanActivateFn.
const loginCanActivate: CanActivateFn = (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => 
   inject(LoginService).userCanActivate(route, state); 
Step-4 : In standalone application to enable routing, component needs to import RouterModule as below.
@Component({
   ------
   imports: [RouterModule],
}) 
Step-5 : In standalone application, use provideRouter to configure routes.
export const APP_CONFIG: ApplicationConfig = {
  providers: [
    provideRouter(ROUTES),
  ]
}; 

3. Using Multiple CanActivateFn

Here I will create canActivate guard that will be activated only when user is logged in and in active state. As we know that canActivate accepts an array of CanActivateFn. Here I will create two instances of CanActivateFn, one service to check login status and another service to check user active status.
Step-1 : In my code, I am creating loginCanActivate and userCanActivate as instances of CanActivateFn.
const loginCanActivate: CanActivateFn = (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => 
   inject(LoginService).canActivate(route, state);
   
const userCanActivate: CanActivateFn = (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) =>
   inject(UserService).canActivate(); 
   
export const ROUTES: Routes = [
    { path: 'bookdetail',
      component: BookComponent,
      canActivate: [loginCanActivate, userCanActivate]
    }
]; 
The canActivate guard will allow to navigate /bookdetail route only when both CanActivateFn instances as loginCanActivate and userCanActivate return true. If any of these CanActivateFn instances returns false, user will not be able to visit that route.
Step-2 : I have already created the login service above. We can define UserService class as following.
@Injectable({
  providedIn: 'root'
})
export class UserService {
  canActivate(): boolean {
    if (this.isUserActive()) {
      return true;
    }
    return false;
  }
  isUserActive() {
    return true;
  }
} 

4. mapToCanActivate

1. mapToCanActivate maps an array of injectable classes having method named as canActivate to an array of CanActivateFn instances.
2. mapToCanActivate shortens the code to create instances of CanActivateFn.
3. It is used as following.
export const ROUTES: Routes = [
    { 
      path: 'bookdetail',
      component: BookComponent,
      canActivate: mapToCanActivate([LoginService, UserService])
    },
]; 
4. The injectable service classes must have a method named as canActivate that should have a return type matching to CanActivateFn return type signature. If the parameters of this canActivate method contains ActivatedRouteSnapshot and RouterStateSnapshot, they will be injected by mapToCanActivate function.
In our demo application, I have injectable services as LoginService and UserService having canActivate method.
@Injectable({
  providedIn: 'root'
})
export class LoginService {
  canActivate(): boolean {
  	------
    return true;
  }
}

@Injectable({
  providedIn: 'root'
})
export class UserService {
  canActivate(): boolean {
  	------
    return true;
  }
} 
If the injectable services used by mapToCanActivate, do not have canActivate method, then code will not compile.

5. Conclusion

To implement canActivate route guard, use CanActivateFn signature. If each instance of CanActivateFn returns true only when user will be able to activate that route. We can also use mapToCanActivate function that will shorten the code to implement canActivate route guard. mapToCanActivate uses injectable services containing a method named as canActivate.

6. References

7. Download Source Code

POSTED BY
ARVIND RAI
ARVIND RAI
LEARN MORE








©2024 concretepage.com | Privacy Policy | Contact Us