Welcome to the lesson on building guards in Angular! In this lesson, we'll explore how route guards can help control access to different parts of your Angular application. Route guards are essential for ensuring that only authorized users can access certain routes, enhancing the security and functionality of your application. By the end of this lesson, you'll be equipped to implement basic route guards, specifically focusing on the CanActivate
, CanDeactivate
, Resolve
, and CanMatch
guards. Let's dive in! 🚀
The CanActivate
guard is a powerful tool for controlling access to routes based on user authentication. It checks whether a user is allowed to navigate to a specific route. If the user is not authorized, the guard can redirect them to a different route, such as a login page.
Here's a simple example of how a CanActivate
guard might be structured:
TypeScript1import { Injectable } from '@angular/core'; 2import { CanActivate, Router } from '@angular/router'; 3 4@Injectable({ providedIn: 'root' }) 5export class AuthGuard implements CanActivate { 6 constructor(private authService: AuthService, private router: Router) {} 7 8 canActivate(): boolean { 9 if (this.authService.isLoggedIn()) { 10 return true; 11 } else { 12 this.router.navigate(['/login']); 13 return false; 14 } 15 } 16}
In this example, the AuthGuard
class implements the CanActivate
interface. It uses an AuthService
to check if the user is logged in. If the user is authenticated, the guard allows access to the route by returning true
. Otherwise, it redirects the user to the login page and returns false
.
The CanDeactivate
guard is used to prevent users from accidentally leaving a route when there are unsaved changes. It prompts the user to confirm navigation away from the current route, which can help prevent data loss.
Here's an example of a CanDeactivate
guard:
TypeScript1import { Injectable } from '@angular/core'; 2import { CanDeactivate } from '@angular/router'; 3import { Observable } from 'rxjs'; 4 5export interface CanComponentDeactivate { 6 canDeactivate: () => Observable<boolean> | Promise<boolean> | boolean; 7} 8 9@Injectable({ providedIn: 'root' }) 10export class CanDeactivateGuard implements CanDeactivate<CanComponentDeactivate> { 11 canDeactivate(component: CanComponentDeactivate): Observable<boolean> | Promise<boolean> | boolean { 12 return component.canDeactivate ? component.canDeactivate() : true; 13 } 14}
In this example, the guard checks if the component implements the CanComponentDeactivate
interface and calls its canDeactivate
method to determine if navigation should proceed.
The Resolve
guard is used to pre-fetch data before a route is activated. This ensures that the component has all the necessary data when it loads, improving the user experience by reducing loading times within the component.
Here's an example of a Resolve
guard:
TypeScript1import { Injectable } from '@angular/core'; 2import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; 3import { Observable } from 'rxjs'; 4import { DataService } from './data.service'; 5 6@Injectable({ providedIn: 'root' }) 7export class DataResolver implements Resolve<any> { 8 constructor(private dataService: DataService) {} 9 10 resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any> { 11 return this.dataService.getData(); 12 } 13}
In this example, the DataResolver
uses a DataService
to fetch data before the route is activated.
The CanMatch
guard determines if a route can be matched, which is useful for lazy loading modules based on certain conditions, such as user roles or permissions.
Here's an example of a CanMatch
guard:
TypeScript1import { Injectable } from '@angular/core'; 2import { CanMatch, Route, UrlSegment } from '@angular/router'; 3 4@Injectable({ providedIn: 'root' }) 5export class RoleGuard implements CanMatch { 6 constructor(private authService: AuthService) {} 7 8 canMatch(route: Route, segments: UrlSegment[]): boolean { 9 return this.authService.hasRole(route.data?.role); 10 } 11}
In this example, the RoleGuard
checks if the user has the required role to match the route. If this is not the case, the routing will continue down the routes
array to try to match other routes.
Once you've implemented the guards, the next step is to integrate them into your Angular routing module. This involves specifying which routes should be protected by each guard.
Here's an example of integrating multiple guards into your routing configuration:
TypeScript1export const routes: Routes = [ 2 { path: 'dashboard', component: DashboardComponent, canActivate: [AuthGuard], resolve: { data: DataResolver } }, 3 { path: 'edit', component: EditComponent, canDeactivate: [CanDeactivateGuard] }, 4 { path: 'admin', component: DashboardComponent, canMatch: [RoleGuard], data: { role: 'admin' } }, 5 { path: 'login', component: LoginComponent }, 6 { path: '', redirectTo: '/login', pathMatch: 'full' }, 7 { path: '**', redirectTo: '/login' } 8];
In this routing configuration, different routes are protected by various guards, ensuring that only authorized users can access specific parts of the application.
In this lesson, we've explored the concept of route guards in Angular, focusing on the CanActivate
, CanDeactivate
, Resolve
, and CanMatch
guards. We've learned how to implement these guards to protect routes based on user authentication, unsaved changes, pre-fetched data, and user roles. This knowledge is crucial for building secure and user-friendly Angular applications.
As you move forward, you'll have the opportunity to practice implementing route guards in the exercises that follow this lesson. These exercises will reinforce your understanding and help you apply what you've learned in real-world scenarios. Keep exploring and experimenting with these guards to enhance your application's security and functionality. Happy coding! 🎉