Dev Study
← 解説「ルートガード — 入ってよいかを判定する」に戻る

サンプルコードで身につける: ルートガード — 入ってよいかを判定する

解説で学んだ概念を、5つの具体例で確認します。素朴な例から実務寄りの例へと進みます。

1最小の CanActivateFn

ガードの骨組みだけを示した最小例です。CanActivateFn 型の関数を作り、true を返せば遷移が許可されます。まだ判定ロジックはありませんが、「ガード=遷移前に呼ばれて可否を返す関数」という形をまず押さえるための例です。

import { CanActivateFn } from '@angular/router';

// 遷移の直前に呼ばれ、true を返せば遷移が続行される
export const demoGuard: CanActivateFn = () => {
  console.log('遷移しようとしています');
  return true;
};

// ルート定義側:
// { path: 'mypage', component: MyPage, canActivate: [demoGuard] }

2ログインチェックとリダイレクト

認証ガードの定番形です。ガードはDIコンテキストで呼ばれるため、関数の中で inject() が使えます。未ログイン時は false ではなく router.parseUrl('/login') を返すことで、遷移を止めるだけでなくログイン画面へ誘導できます。

import { inject } from '@angular/core';
import { CanActivateFn, Router } from '@angular/router';
import { AuthService } from './auth.service';

export const authGuard: CanActivateFn = () => {
  // 関数だが inject() が使える
  const auth = inject(AuthService);
  const router = inject(Router);

  if (auth.isLoggedIn()) {
    return true;
  }
  // UrlTree を返すと、そのURLへリダイレクトされる
  return router.parseUrl('/login');
};

31つのガードを複数のルートで使い回す

ガードは関数なので、canActivate の配列に書くだけで何ルートにでも付けられます。「ログインが必要なページ」が増えても、ガード自体は1つ書けば済みます。各コンポーネントに認証チェックをコピーして回る必要がないのが、ルート側で守る利点です。

import { Routes } from '@angular/router';
import { authGuard } from './auth.guard';
import { Login } from './login';
import { MyPage } from './my-page';
import { OrderHistory } from './order-history';

export const routes: Routes = [
  // ログイン画面自体にはガードを付けない
  { path: 'login', component: Login },

  // ログインが必要なページに同じガードを並べる
  { path: 'mypage', component: MyPage, canActivate: [authGuard] },
  { path: 'orders', component: OrderHistory, canActivate: [authGuard] },
];

4遷移先の情報を使って権限を判定する

CanActivateFn は引数として遷移先のルート情報(ActivatedRouteSnapshot)を受け取れます。ルート定義の data に必要な権限を書いておき、ガード側でそれを読んで判定する、役割ベースの権限チェックの定番パターンです。1つのガードでページごとに異なる条件を扱えます。

import { inject } from '@angular/core';
import { CanActivateFn, Router } from '@angular/router';
import { AuthService } from './auth.service';

// 第1引数で遷移先のルート情報を受け取れる
export const roleGuard: CanActivateFn = (route) => {
  const auth = inject(AuthService);
  const router = inject(Router);

  // ルート定義の data に書いた必要権限を読む
  const requiredRole = route.data['role'] as string;

  if (auth.hasRole(requiredRole)) {
    return true;
  }
  return router.parseUrl('/forbidden');
};

// ルート定義側:
// { path: 'admin', component: Admin,
//   canActivate: [roleGuard], data: { role: 'admin' } }

5canDeactivate で「離れてよいか」を確認する

ガードには「入ってよいか」だけでなく「離れてよいか」を判定する canDeactivate もあります。編集中のフォームから離れる前に確認ダイアログを出す、データ消失防止の定番です。コンポーネント側に「保存済みか」を返すメソッドを用意し、ガードから呼び出します。

import { CanDeactivateFn } from '@angular/router';

// 編集画面が実装すべきインターフェース
export interface HasUnsavedChanges {
  hasUnsavedChanges(): boolean;
}

// 離れる直前に呼ばれる。false を返すと遷移が止まる
export const unsavedChangesGuard: CanDeactivateFn<HasUnsavedChanges> = (
  component
) => {
  if (component.hasUnsavedChanges()) {
    return confirm('保存していない変更があります。移動しますか?');
  }
  return true;
};

// ルート定義側:
// { path: 'edit', component: EditPage,
//   canDeactivate: [unsavedChangesGuard] }