Angularで知っておきたい3つの機能

# はじめに

本稿は[システムアイ Advent Calendar 2022](https://qiita.com/advent-calendar/2022/systemi22)の記事となります。

Angular(アンギュラー)はGoogle社によって開発されているTypeScriptベースのフロントエンドフレームワークです。React、Vue.jsと並んで人気があるフレークワークの一つとなっています。(厳密にはReact、Vue.jsはフレームワークではなくライブラリです)

また、Angularの特徴としてフルスタック(全部入り)のフレームワークとなり、標準の機能だけでアプリケーションが作ることができます。

今回はそんなAngularの知っておきたい基本の3つの機能をお送りします。


# Service
Angularのサービス(Service)という機能があります。サービスを使うとコンポーネントからロジック部分を分離する(別ファイルに分ける)ことができます。
例えば、サーバーからのデータ取得、入力チェック、ログ出力などをサービスに記述することで、コンポーネントから左記ロジックを切り離せるため、サービス、コンポーネントの再利用性があがります。

また、サービスをシングルトン(単一インスタンス)にし、アプリケーションにひとつだけ存在させることができます。サービスを定義するInjectableデコレータに`providedIn: 'root'`を書くだけでシングルトンにでき、アプリケーション内で利用することができます。(※)

Angular CLIが導入されている環境であれば、下記コマンドを実行するだけで簡単にシングルトンのサービスが作成できます。

```
ng generate service user
```

```src/app/user.service.ts
import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root',
})
export class UserService {
}
```

:::note warn
※Angularが6.0未満の場合は、`providedIn: 'root'`ではなく、AppModuleにサービスを定義する必要があります。

```src/app/app.module.ts
@NgModule({
  ...
  providers: [UserService],
  ...
})
```
:::

# HttpInterceptor
HTTPリクエスト/レスポンスに共通の前処理を挟み込むことができます。
例えばリクエストヘッダーに認証トークンを設定したり、レスポンスの結果を共通的に扱いたい場合に本機能を利用します。

```src/app/token.interceptor.ts
import { Injectable } from '@angular/core';
import {
  HttpEvent, HttpInterceptor, HttpHandler, HttpRequest, HttpResponse
} from '@angular/common/http';

import { Observable,tap } from 'rxjs';

@Injectable()
export class TokenInterceptor implements HttpInterceptor {

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    // sesion storageに保存されているトークンを取り出す
    const token = sessionStorage.getItem('token');

    // 全てのリクエストのAuthorizationヘッダーにトークンをセット
    const newRequest = request.clone({
        headers: request.headers.set(
            'Authorization', `Bearer ${token}`
        )
    });
    return next.handle(newRequest);
  }
}
```

レスポンスヘッダーからトークンを取り出す場合は`return next.handle(newRequest);`を下記のように書き換えます。
```src/app/token.interceptor.ts
...
    return next.handle(newRequest).pipe(
      tap((res) =>{
        if(res instanceof HttpResponse){
          const token = res.headers.get('token')
          if(token){
            sessionStorage.setItem('token',token)
          }
        }
      })
    );
...
```

# Guard
Angularのガード(Guard)を使えば、ルーティング前後に処理を挟むことができます。
例えば、次のページへ遷移する際に権限のチェックをする際などにシーンに利用します。

こちらもAngular CLIが導入されている環境であれば、下記コマンドを実行するだけで簡単にガードを作成できます。
```
ng generate guard auth
```

```
? Which interfaces would you like to implement? CanActivate
CREATE src/app/auth.guard.spec.ts (331 bytes)
CREATE src/app/auth.guard.ts (457 bytes)
PS C:\Users\systemi\work\angular\my-app> 
```

```src/app/auth.guard.ts
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot, UrlTree } from '@angular/router';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class AuthGuard implements CanActivate {
  canActivate(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
    return true;
  }
  
}
```

canActivateのリターンがtrueの場合は通常通りページに遷移、falseの場合は遷移不可にできます。
下記はガードを書き換え、session storageにトークンがない場合はログインページに転送するようにしています。

```src/app/auth.guard.ts
...
  constructor(
    private router: Router
  ) {}

  canActivate(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {

      if (!sessionStorage.getItem('token')) {
        this.router.navigate(['login']);
        return false;
      }
      return true;
  }
```

ガードしたいルートに対してガードを記述します。

```src/app/app-routing.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule, Routes } from '@angular/router';
import { LoginComponent } from './login/login.component'
import { AuthGuard } from './auth.guard';

const routes: Routes = [
  { path: 'login', component: LoginComponent , canActivate: [AuthGuard] }
];

@NgModule({
  imports: [CommonModule,RouterModule.forRoot(routes)],
  exports: [RouterModule],
})
export class AppRoutingModule { }
```




# まとめ
Angularで知っておきたい3つの機能をご紹介しました。
そのほかにもディレクティブ、パイプ、バリデーション、遅延ロードなどたくさんの機能を標準で備えたAngularにぜひ触れてみてください。

Angular
https://angular.jp/