Advanced Routing: Route Guards and Resolvers
In the previous lessons of our Complete Angular Masterclass, we learned how to set up basic navigation. However, real-world applications require more than just moving from one page to another. You often need to restrict access to certain pages (like an Admin Dashboard) or ensure data is fully loaded before a component appears. This is where Route Guards and Resolvers come into play.
What are Route Guards?
Route Guards are interfaces provided by Angular that allow or deny navigation to a requested route based on specific conditions. Think of them as security checkpoints at an airport. If you don't have a ticket (authentication), you cannot pass through the gate (the route).
Types of Route Guards
- CanActivate: Determines if a user can visit a route. Often used for authentication.
- CanActivateChild: Determines if a user can visit the child routes of a specific route.
- CanDeactivate: Determines if a user can leave a route. Useful for warning users about unsaved changes in a form.
- CanMatch: A modern guard that decides if a route should even be considered for matching.
The Navigation Flowchart
Understanding the order of execution is critical for debugging. Here is the logical flow when a user clicks a link:
[User Clicks Link]
|
[CanMatch Guards Run]
|
[CanActivate Guards Run]
|
[Resolvers Fetch Data]
|
[Component Initialized]
Implementing a Functional CanActivate Guard
In modern Angular (v15+), functional guards are preferred over class-based guards for their simplicity and performance. Let's look at a real-world example of an authGuard.
import { inject } from '@angular/core';
import { Router } from '@angular/router';
import { AuthService } from './auth.service';
export const authGuard = () => {
const authService = inject(AuthService);
const router = inject(Router);
if (authService.isLoggedIn()) {
return true;
} else {
router.navigate(['/login']);
return false;
}
};
To use this guard, you simply add it to your app.routes.ts file:
{ path: 'dashboard', component: DashboardComponent, canActivate: [authGuard] }
What are Resolvers?
Have you ever navigated to a page and seen a blank screen for a split second while the data loads? Resolvers solve this. A Resolver acts as a data pre-fetcher. It fetches the required data before the component is even instantiated.
Example: User Profile Resolver
Instead of the component calling a service in ngOnInit, the resolver handles it:
export const userResolver = (route) => {
const userService = inject(UserService);
const userId = route.paramMap.get('id');
return userService.getUserDetails(userId);
};
In the route configuration:
{
path: 'profile/:id',
component: ProfileComponent,
resolve: { userData: userResolver }
}
Common Mistakes to Avoid
- Infinite Loops: Redirecting to the same route inside a guard without a proper exit condition.
- Blocking Navigation: Forgetting to return
trueorfalsein a guard, which results in the route never loading. - Heavy Resolvers: Putting too much data fetching logic in a resolver. If the API is slow, the user will feel like the app has frozen because the page won't change until the data arrives.
- Not Handling Errors: If a Resolver fails (e.g., 404 error), the route will not load at all. Always include error handling.
Real-World Use Cases
Advanced routing is used in almost every professional Angular application:
- E-commerce: Using
CanDeactivateto ask "Are you sure you want to leave? Your cart will be cleared." - Admin Panels: Using
CanActivateto check if the user has the "Admin" role. - Search Results: Using
Resolversto ensure the search results are ready before showing the results page.
Interview Notes for Developers
- Question: What is the difference between CanActivate and CanMatch?
- Answer: CanActivate runs after the route is matched to decide if the user can enter. CanMatch runs before the route is even selected, allowing you to have two different components for the same path based on conditions.
- Question: How do you access resolved data in a component?
- Answer: By injecting
ActivatedRouteand subscribing to thedataobservable. - Question: Why use functional guards over class-based guards?
- Answer: Functional guards reduce boilerplate, support better tree-shaking, and allow for easier use of the
inject()function.
Summary
In this lesson, we explored how to make our Angular applications more secure and user-friendly using Advanced Routing techniques. We covered Route Guards for protecting paths and Resolvers for pre-fetching data. Mastering these tools is essential for building robust, enterprise-grade applications. In the next topic of our Angular Masterclass, we will dive into Lazy Loading to optimize our application's performance.