Angularの関数ベースGuard(CanActivateFn)の書き方

# 記事の概要
Angularで認証済みの場合のみ遷移させたい画面があり、ルーティングにGuardを適用するためにGuardクラスを作成したところ、implementsしている`CanActivate`がDeprecatedでマークされていることに気づきました。
調べてみると、[Angular15.2からクラスベースのルートGuardは非推奨になっている](https://github.com/angular/angular/blob/main/CHANGELOG.md#deprecations-2)ことがわかりました。
代わりに関数ベースのGuardを使うことが推奨されていますが、日本語の情報が少なかったため書き方を備忘としてメモしておきます。
ただし関数ベースのGuardといっても全種類ではなく`CanActivateFn`に限っての説明となります。

# 先に結論：書き方
### 関数ベースGuardの実装サンプル：
```typescript:guards.ts
export const authGuard: CanActivateFn = () => {
  // 認証管理サービスのinject
  const authService = inject(AuthService);
  // Routerのinject
  const router = inject(Router);
  if (authService.isAuthenticated()) {
    // 認証済みのため（認証後画面への）ナビゲーションOK
    return true;
  } else {
    // 認証未済のため認証ページに飛ばす
    return router.parseUrl('auth');
  }
};
```
- ~.routes.tsに直接定義してももちろん大丈夫です。
- `CanActivateFn`のパラメータ`route: ActivatedRouteSnapshot, state: RouterStateSnapshot`は非使用のため省略しています。
### Routes定義の実装サンプル：
```typescript:app.routes.ts
export const routes: Routes = [
  {
    path: 'after-auth', // 認証後ページ
    loadComponent: () => import('./pages/after-auth/after-auth.page').then(m => m.AfterAuthPage),
    canActivate: [authGuard] // Guardの登録
  },
  {
    path: 'auth', // 認証ページ
    loadComponent: () => import('./pages/auth/auth.page').then(m => m.AuthPage),
  }
];
```
`CanActivate:`に配列でGuardを登録したら実装完了です。
# CanAcvivateFnについて
`CanAcvivate`の代わりに実装するべき`CanActivateFn`について見てみます。

https://angular.jp/api/router/CanActivateFn

型エイリアスはこのようになっています。基本的には`boolean`か`UrlTree`（orこれらのObservable, Promise）を返す関数を実装すれば良いです。
パラメータのActivatedRouteSnapshot, RouterStateSnapshotは必須ではありません。
```typescript
type CanActivateFn = (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree;
```
説明も見てみます。
> If all guards return true, navigation continues. If any guard returns false, navigation is cancelled. If any guard returns a UrlTree, the current navigation is cancelled and a new navigation begins to the UrlTree returned from the guard.

「全てのguardが`true`を返した場合はナビゲーション続行。いずれかのguardが`false`を返したらナビゲーションはキャンセル。いずれかのguardが`UrlTree`を返したら、それまでのナビゲーションはキャンセルされて`UrlTree`へのナビゲーションがスタートする。」
・・・ということのようです。
元々のナビゲーションを許容する場合は単に`true`を返却すれば良いです。
一方で、元々のナビゲーションをキャンセルする場合、代わりに遷移させたいパスがあることがほとんどだと思います。
この場合の実装として、例えば以下のような感じで紹介している記事を見かけます。
```typescript
/** 中略 */
const router = inject(Router)
if (canNavigate()) {
  return true;
else {
  // 代わりのパスにナビゲートした上で元々のナビゲーションはキャンセル
  router.navigate(['auth']);
  return false;
}
```
`Router.navigate`した上で`false`を返すこれでも動きますが、副作用を起こしている感が若干キレイじゃない気がしてしまいます。　（関数ベースだと特に）
代わりにUrlTreeを返す（`Router.parseUrl()`）方がキレイに同じことを実現できると[こちらの記事](https://qiita.com/MasanobuAkiba/items/983904fb8579e2ddb553)で知ったため、サンプル実装でもそうしています。
```typescript
/** 中略 */
const router = inject(Router)
if (canNavigate()) {
  return true;
else {
  // 代わりのパスにナビゲートした上で元々のナビゲーションはキャンセル
  return router.parseUrl('auth');
}
```
# おまけ：UrlTreeについて
`UrlTree`というクラスについて聞いたことが無かったためついでに少し調べてみました。

https://angular.jp/api/router/UrlTree

>UrlTree is a data structure that provides a lot of affordances in dealing with URLs

「UrlTreeはURLに関する様々な利便性を提供するデータ構造」のようです。
Usage notesがとてもわかりやすいため引用します。
```typescript
@Component({templateUrl:'template.html'})
class MyComponent {
  constructor(router: Router) {
    const tree: UrlTree =
      router.parseUrl('/team/33/(user/victor//support:help)?debug=true#fragment');
    const f = tree.fragment; // return 'fragment'
    const q = tree.queryParams; // returns {debug: 'true'}
    const g: UrlSegmentGroup = tree.root.children[PRIMARY_OUTLET];
    const s: UrlSegment[] = g.segments; // returns 2 segments 'team' and '33'
    g.children[PRIMARY_OUTLET].segments; // returns 2 segments 'user' and 'victor'
    g.children['support'].segments; // return 1 segment 'help'
  }
}
```
様々なプロパティによって、パラメータなどURLを構成するパーツにアクセスできるようです。
また、UrlTreeを作成する手段としていくつかあるようで：
- `Router`クラスの`parseUrl(url: string): UrlTree;`はURL文字列をシンプルにパースしてUrlTreeを作成できるようです。（ https://angular.jp/api/router/Router#parseurl ）
- 同じく`Router`クラスの`createUrlTree(commands: any[], navigationExtras?: UrlCreationOptions): UrlTree;`はより柔軟にUrlTreeを組み立てられるようです。（ https://angular.jp/api/router/Router#createurltree ）
パラメータ等を付与したURLに遷移させる場合はこちらを使うのが便利かと思いました。

# まとめ
関数ベースGuard（CanActivateFn）の実装方法とUrlTreeについて紹介しました。
最近のAngularやRxJSで非推奨になっている機能や代わりに推奨されている書き方について、意外と日本語の記事が少なかったりするため、少しでもこの記事が役に立てば嬉しいです。

# 参考資料

https://medium.com/ngconf/functional-route-guards-in-angular-8829f0e4ca5c

https://qiita.com/MasanobuAkiba/items/983904fb8579e2ddb553