Route guards
A guard is a function the router calls before navigating to a page. It decides: let the user in, refuse, or send them to another page. This is how you close, for example, the new post form to guests.
An auth service
The guard needs to know whether the user is logged in. Let's make a simple service without a real server:
ng generate service auth// src/app/auth.ts
import { Injectable, computed, signal } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class Auth {
readonly token = signal<string | null>(null);
readonly isLoggedIn = computed(() => this.token() !== null);
login() {
this.token.set('demo-token');
}
logout() {
this.token.set(null);
}
}In a real application login() would send the login and password to the server and store the token it gets back.
Creating a guard
ng generate guard authThe CLI asks which kind of guard to create — choose CanActivate. The file src/app/auth-guard.ts appears (before version 20 — auth.guard.ts):
// src/app/auth-guard.ts
import { inject } from '@angular/core';
import { CanActivateFn, Router } from '@angular/router';
import { Auth } from './auth';
export const authGuard: CanActivateFn = () => {
const auth = inject(Auth);
const router = inject(Router);
if (auth.isLoggedIn()) {
return true;
}
return router.createUrlTree(['/login']);
};If the function returns true, navigation goes ahead. If it returns an address created by createUrlTree(), the router redirects there. false simply cancels the navigation.
Attaching it to a route
The guard goes into the canActivate array:
// src/app/app.routes.ts
export const routes: Routes = [
{ path: 'login', component: Login },
{ path: 'posts', component: PostList },
{ path: 'posts/new', component: PostForm, canActivate: [authGuard] },
{ path: 'posts/:id', component: PostDetail },
];Open http://localhost:4200/posts/new — instead of the form, /login opens. The posts/new route is above posts/:id, otherwise the word new would be taken as a post id.
The login page
ng generate component login// src/app/login/login.ts
import { Component, inject } from '@angular/core';
import { Router } from '@angular/router';
import { Auth } from '../auth';
@Component({
selector: 'app-login',
template: `<button (click)="enter()">Log in as Anna</button>`,
})
export class Login {
private auth = inject(Auth);
private router = inject(Router);
enter() {
this.auth.login();
this.router.navigate(['/posts/new']);
}
}Click "Log in as Anna" — the guard lets you through, and the new post form opens.
Other kinds of guards
| Route property | When it runs |
|---|---|
canActivate | Before entering a page |
canActivateChild | Before entering any child page |
canDeactivate | Before leaving a page (for example, a form has unsaved changes) |
canMatch | While choosing a route: if false, the next one is checked |
Common mistake
Treating a guard as data protection. A guard runs in the browser, and any user can bypass it with the developer tools. It only improves convenience: it hides pages that are not relevant. The real permission check is always done by the server on every request.