Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

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 auth

The 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 propertyWhen it runs
canActivateBefore entering a page
canActivateChildBefore entering any child page
canDeactivateBefore leaving a page (for example, a form has unsaved changes)
canMatchWhile 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.

Thanks for reading!

Donate to the site — we work hard for you.