Angular Route Guards: Old vs New Approach
This blog post explores the old and new approaches to implementing route guards in Angular applications. It provides generalized examples of both methods and highlights the advantages of the new function-based approach, such as improved tree-shakability and better performance.
In Angular applications, route guards are used to control navigation and access to specific routes based on certain conditions. They provide a way to protect routes from unauthorized access or redirect users to different routes based on their authentication status or other criteria. Angular has introduced a new approach to implementing route guards, which differs from the old, deprecated method. In this post, we'll explore both approaches and provide generalized examples.
The Old Approach: CanActivate Interface
import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';
import { Observable } from 'rxjs';
@Injectable()
export class AuthGuard implements CanActivate {
constructor(private router: Router, private authService: AuthService) {}
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Observable<boolean> | Promise<boolean> | boolean {
// Check if the user is authenticated
if (this.authService.isAuthenticated()) {
return true; // Allow navigation
} else {
this.router.navigate(['/login']); // Redirect to the login page
return false; // Prevent navigation
}
}
}
In this example, the AuthGuard class implements the CanActivate interface and provides the canActivate method. This method checks if the user is authenticated using the AuthService. If the user is authenticated, it returns true, allowing navigation to the requested route. If the user is not authenticated, it redirects them to the login page and returns false, preventing navigation.
The New Approach: CanActivateFn
In more recent versions of Angular, the new approach to implementing route guards is to use functions instead of classes. These functions are defined as CanActivateFn, CanActivateChildFn, CanDeactivateFn, and CanMatchFn. In this post, we'll focus on the CanActivateFn.
The CanActivateFn is a function that returns an observable of type boolean | UrlTree. If the observable emits true, navigation is allowed. If it emits a UrlTree, the user is redirected to the specified route.
Here's a generalized example of the new approach using the CanActivateFn:
import { inject } from '@angular/core';
import { CanActivateFn, Router, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree } from '@angular/router';
import { map, Observable } from 'rxjs';
import { AuthService } from './auth.service';
export const authGuard: CanActivateFn = (
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Observable<boolean | UrlTree> => {
const authService = inject(AuthService);
const router = inject(Router);
return authService.isAuthenticated().pipe(
map(isAuthenticated => {
if (isAuthenticated) {
return true; // Allow navigation
} else {
return router.createUrlTree(['/login']); // Redirect to the login page
}
})
);
};
In this example, the authGuard function is defined as a CanActivateFn. It injects the AuthService and Router services using the inject function from Angular's dependency injection system. The authService.isAuthenticated() observable is piped through the map operator, which checks if the user is authenticated. If the user is authenticated, it emits true, allowing navigation. If the user is not authenticated, it creates a UrlTree using router.createUrlTree(['/login']), which redirects the user to the login page.
Advantages of the New Approach
The new approach using CanActivateFn and other function-based route guards has several advantages:
- Improved tree-shakability: By using functions instead of classes, Angular's tree-shaking process can better optimize the code by removing unused code, resulting in smaller bundle sizes.
- Better performance: Function-based route guards are more lightweight and have a lower memory footprint compared to class-based guards.
- Easier to read and maintain: The function-based approach is more concise and easier to read and maintain, especially for simple guards.
- Dependency injection: The new approach leverages Angular's dependency injection system, making it easier to inject services and other dependencies into the guard functions.
Conclusion
Both the old and new approaches to implementing route guards in Angular are valid and serve their purposes. However, the new function-based approach introduced in recent versions of Angular offers several advantages, including improved tree-shakability, better performance, and easier readability and maintainability. As Angular continues to evolve, it's recommended to adopt the new approach for route guards and other aspects of the framework to take advantage of the latest improvements and best practices.
