Angular Enterprise Dashboard - Phase 2.2: Securing the Perimeter - Functional Route Guards

In the previous part of this series, we built our Reactive Authentication Service. Now, we need to bridge the gap between our authentication state and our application’s navigation.
Security as a Function: Modernizing the Perimeter
Enter Functional Route Guards. In this post, weโll see how to leverage Angular’s modern functional patterns to protect our routes with minimal friction and maximum clarity.
1 ๐๏ธ The Shift from Classes to Functions
Angular has evolved. The days of creating a class implementing CanActivate are behind us. Modern guards are just functions.
1.1 Why go functional?
- Lightweight: No need for the boilerplate of a class and DI constructor.
- Composable: Functions are easier to combine and reason about.
- DI-Friendly: You can still use
inject()to get any service you need.
2 ๐ก๏ธ Protecting the Dashboard: authGuard
Our goal is simple: if a user is not authenticated, they shouldn’t see the dashboard. Instead, they should be redirected to the login page.
// auth.guard.ts
export const authGuard: CanActivateFn = () => {
const authService = inject(AuthService);
const router = inject(Router);
if (authService.isAuthenticated()) {
return true; // Proceed with navigation
}
// Redirect to login if not authenticated
return router.createUrlTree(["/login"]);
};2.1 The Logic Flow
sequenceDiagram
participant U as User
participant R as Router
participant G as authGuard
participant A as AuthService
U->>R: Navigates to /dashboard
R->>G: Checks CanActivate
G->>A: isAuthenticated() ?
A-->>G: Returns false
G->>R: Returns UrlTree(/login)
R->>U: Shows Login Page
3 ๐ช The Guest Guard: guestGuard
Security works both ways. We also want to prevent authenticated users from going back to the login pageโitโs a better UX to keep them in their workspace.
// guest.guard.ts
export const guestGuard: CanActivateFn = () => {
const authService = inject(AuthService);
const router = inject(Router);
if (!authService.isAuthenticated()) {
return true; // Guests are welcome!
}
return router.createUrlTree(["/dashboard"]);
};4 ๐ฆ Integrating into the Router
The real beauty of functional guards is how clean the route configuration becomes. You simply list them in the canActivate array.
// app.routes.ts
export const routes: Routes = [
{
path: "login",
loadComponent: () => import("./features/auth/login.component"),
canActivate: [guestGuard], // Only for unauthenticated users
},
{
path: "",
canActivate: [authGuard], // The "Outer Wall"
loadComponent: () => import("./core/layout/app-shell.component"),
children: [
{
path: "dashboard",
loadComponent: () => import("./features/dashboard.component"),
},
],
},
];5 ๐ The Teaching Moment: Signals + Guards
Notice how our guards use authService.isAuthenticated(). Because this is a Computed Signal, itโs lightning-fast. The Router doesn’t need to wait for an Observable to emit; it gets the truth immediately.
6 Coming Up Next
We’ve secured our routes, but where do our users actually “live”? In Phase 2.3: Architecture of the Shell, we’ll build the premium layout that hosts our dashboard features.
Found this useful? This dashboard is designed to showcase enterprise patterns. Check out the series history in the docs/blog directory!