AngularでREST APIにアクセスする

Angularで、REST APIにアクセスしてみます。
概要は以下の通りです。

* Node.js+Expressで開発したAPIサーバーにアクセスする
* login APIにアクセスし、ユーザIDとパスワードを渡し認証する
* 認証結果を受け取り、その中のJWTを保存する

# authサービスを作成
Angularでは、APIにアクセスする場合は、serviceクラスを作成します。
以下のコマンドで、serviceクラスのひな形を作成できます。

> ng g service services/auth

```typescript:auth.service.ts
import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class AuthService {

  constructor() { }
}
```
@Injectableというのが付いていますね。これは、「他のサービスを使ってませんよ」という宣言です。所謂、依存性注入(DI)ですが、サービス間の依存をなくす事で独立性が高まり、メンテナンス性が向上するなどの効果があります。ひな形として作成されるので、削除しないようにしましょう。

# serviceの登録
作成したサービスは、Angularが予め指定したものを指定した方法で作成してくれます。
具体的には、app.modules.ts、app.component.ts、xxx.component.tsのいずれかの(もしくは意図的に複数)providersにその内容を記載します。
省略した記載方法もありますが、厳密にはこのようになっています。
> { provide: サービス名, サービス生成方法: サービスクラス名, multi: true/false }
この中で、サービス生成方法(つまりサービスの生成の仕方)、multiは気を付けて指定する必要があります。

* サービス生成方法

|指定|特徴|
|:--------------|:--------------------------------------|
|useClass|Angularがサービスを生成する(newする)時に、常に新しいオブジェクトとして生成します。常に新しいオブジェクトとして生成されると言うことなので、サービスの中で保持している情報は、別々に管理できると言うことになります。|
|useValue|Angularがサービスを生成するときに、無ければ新しく作り、あれば作成済みのものを使用します。結果的に一つのみオブジェクトが生成されることになりますので、サービスの中で保持されている情報は、同じものと言うことになります。
|useExisting|useValueと似ています。既存のサービスに別名を付けるために利用します。
|useFactory|サービスの処理をカスタマイズするときに利用します。サービスを利用するに当たり独自の初期処理などを行う場合に便利です。

* multi: 同じサービスが登録された場合、後に書かれたものが優先されます(falseの場合)。サービスを配列として管理したい場合は、trueを指定します。

今回はログインを行うサービスを登録します。ログインする処理のみを実装し、ログイン後に発行されるトークンはJWTはlocalStorageで管理しますので、useClassを利用します。

```typescript:app.component.ts
import { Component } from '@angular/core';

import { AuthService } from './services/auth.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.pug',
  styles: [],
  providers: [
    { provide: AuthService, useClass: AuthService }
  ],
})
export class AppComponent {
  title = 'root';
}
```

# loginモデルの作成
ログインするために必要な情報を格納するloginモデルを作成します。

> ng g model models/login

```typescript:login.ts
export class Login {
    constructor (
        public email: string,
        public password: string
    ) { }
}
```

# ログイン処理の実装
先に作成したAuthServiceクラスにログイン処理を実装します。
REST APIにPOSTするために、@angular/common/httpのHttpClientを使います。
あと、普通は復帰値としてObservableを使うようですが、Promiseに慣れているので、敢えてPromiseで実装しています。

```typescript:auth.service.ts
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import 'rxjs/add/operator/toPromise';
import * as moment from 'moment';

import { Login } from '../models/login';

@Injectable({
  providedIn: 'root'
})
export class AuthService {

  constructor(private http: HttpClient) { }

  public login(login: Login): Promise<string> {
    const httpOptions = {
      headers: new HttpHeaders({
        'Content-Type': 'application/x-www-form-urlencoded'
      })
    };

    // login APIにPOSTする
    return this.http.post('/api/login', login, httpOptions)
    .toPromise()
    .then((result: any) => {
        // 認証結果がsuccessならトークンを返す
        if (result.result === 'success') {

          // 取得したトークンをLocalStorageに保存する
          const expiresAt = moment().add(result.token, 'second');
          localStorage.setItem('id_token', result.token);
          localStorage.setItem('expires_at', JSON.stringify(expiresAt.valueOf()));

          return result.result;
        }
        // 認証結果がsuccessでなければエラーメッセージを返す
        else {
          return Promise.reject(result.message);
        }
      }
    )
    .catch((err: any) => {
        return Promise.reject(err.statusText);
      }
    );
  }

  logout() {
    // LocalStorageに保存したトークンを削除する
    localStorage.remoteItem('id_token');
    localStorage.removeItem('expires_at');
  }

  isLogin(): Boolean {
    return moment().isBefore(this.getExpiration());
  }

  getAuthHeader(): string {
    const token = localStorage.getItem('id_token');

    if (token) {
      return 'Bearer ' + token;
    }
    else {
      undefined;
    }
  }

  private getExpiration(): moment.Moment {
    const expiration = localStorage.getItem('expires_at');
    const expiresAt = JSON.parse(expiration);

    return moment(expiresAt);
  }
}
```

# ログインするコンポーネントにログイン処理を実装
最後にログインを行うコンポーネントに、ログイン処理を実装します。

```typescript:login.component.ts
import { Component, OnInit } from '@angular/core';
import { Login } from '../../models/login';
import { AuthService } from '../../services/auth.service';

@Component({
  selector: 'app-login',
  templateUrl: './login.component.pug',
  styleUrls: ['./login.component.styl']
})

export class LoginComponent implements OnInit {

  model: Login = new Login('', '');

  submitted: boolean = false;

  errormsg: string = undefined;

  constructor(private loginservice: AuthService) { }

  ngOnInit() {
  }

  onSubmit() {
    // submitフラグを設定する
    this.submitted = true;
    // エラーメッセージを初期化する
    this.errormsg = undefined;

    // ユーザ認証する
    this.loginservice.login(this.model)
    .then((token: string) => {
    })
    .catch((err: any) => {
      this.errormsg = 'Eメールアドレスまたはパスワードが違います。';
      console.log(err);
    });
  }
}
```