Angular5 で俺式 KOAN スタック Webアプリ構築（4）

# クラサバ通信部の作成
- Angular5で「俺式KOANスタック」でWebアプリ構築するための個人的な備忘録記事。
- 概要については「[こちら](https://qiita.com/tomy0610/items/feaf4ced2ccb66373c15)」を参照。
- 事前に「[バックエンド初期設定](https://qiita.com/tomy0610/items/bb87e2d19572a03c5df3)」が完了していることが前提。
- 本記事まで完了すればWebアプリとして最低限の動作が可能

# 中間部分を作成
クライアントとサーバでやり取りするインターフェースや、どちら側でも使用できる様なモジュール領域を定義します。
普通はそんな構成にしないかもしれませんが、その辺は「俺式KOANスタック」ということで。

## フォルダ および ファイル を追加
* ルートディレクトリ直下に `middle` フォルダを作成し、配下に `exchange`, `base`, `sample`新規に空のファイル `request.ts`, `response.ts`, `sample.request.ts`, `sample.response.ts` を作成して以下のような構成にする

```
middle
 └─ exchange
      ├─ base
      │   ├─ request.ts
      │   └─ response.ts
      │
      └─ sample
          ├─ sample.request.ts
          └─ sample.response.ts

```

## request.ts を記述
`middle/exchange/base/request.ts` を以下の通りにする

```typescript:middle/exchange/base/request.ts
/** 基底リクエスト */
interface IRequest {
    requestId: string;
}
export = IRequest;

```

## response.ts を記述
`middle/exchange/base/response.ts` を以下の通りにする

```typescript:middle/exchange/base/response.ts
/** 基底レスポンス */
interface IResponse {
    status: boolean;
    statusCode: number;
}
export = IResponse;

```

## sample.request.ts を記述
`middle/exchange/sample/sample.request.ts` を以下の通りにする

```typescript:middle/exchange/sample/sample.request.ts
import Request = require('../base/request');

/** サンプル リクエスト */
interface ISampleRequest extends Request {
    num1: number;
    num2: number;
}
export = ISampleRequest;

```

## sample.response.ts を記述
`middle/exchange/sample/sample.response.ts` を以下の通りにする

```typescript:middle/exchange/sample/sample.response.ts
import Response = require('../base/response');

/** サンプル レスポンス */
interface ISampleResponse extends Response {
    result: number;
}
export = ISampleResponse;

```

# サーバ側を対応
## MiddlewareRouter  を修正
`server/middleware.router.ts` を以下の通りに修正する

```typescript:server/middleware.router.ts
import KoaRouter = require('koa-router');

import Request = require('../middle/exchange/base/request');
import Response = require('../middle/exchange/base/response');
import SampleContoroller = require('./app/controller/sample.controller');

/** ミドルウェア ルータ */
class MiddlewareRouter {
    /** 各ルータを束ねたミドルウェアを生成 */
    public routes(): KoaRouter.IMiddleware {
        const koaRouter = new KoaRouter();

        koaRouter.get('/api/get-sample', this.callGet(new SampleContoroller().test));
        koaRouter.post('/api/post-sample', this.callPost(new SampleContoroller().test));

        return koaRouter.routes();
    }

    /**
     * 実行処理の呼び出し(GET)
     * @param controll 実行される処理
     * @returns ルータに設定するミドルウェア
     */
    private callGet(controll: (request: Request) => Promise<Response>): any {
        return async function(ctx: KoaRouter.IRouterContext) {
            const ctxRequest: any = ctx.request;
            const ctxResponse = await controll(ctxRequest.query);
            ctx.body = ctxResponse;
        };
    }

    /**
     * 実行処理の呼び出し(POST)
     * @param controll 実行される処理
     * @returns ルータに設定するミドルウェア
     */
    private callPost(controll: (request: Request) => Promise<Response>): any {
        return async function(ctx: KoaRouter.IRouterContext) {
            const ctxRequest: any = ctx.request;
            const ctxResponse = await controll(ctxRequest.body);
            ctx.body = ctxResponse;
        };
    }
}
export = MiddlewareRouter;

```
> ルート「/」へのGETリクエストメソッドを削除

> ベタ書きのインターフェースを削除し、作成したインターフェースを利用するように変更

> POSTリクエストの処理を追加

## SampleController を修正
`server/app/controller/sample.controller.ts` を以下の通りに修正する

```typescript:server/app/controller/sample.controller.ts
import SampleRequest = require('../../../middle/exchange/sample/sample.request');
import SampleResponse = require('../../../middle/exchange/sample/sample.response');

/** サンプル コントローラ */
class SampleContoroller {
    public async test(request: SampleRequest): Promise<SampleResponse> {

        console.log('=== SampleContoroller # test ===');
        console.log(request);

        return await {
            status: true,
            statusCode: 200,
            result: 1
        } as SampleResponse;
    }
}
export = SampleContoroller;

```
> 「any」型のインターフェースをやめ、作成したインターフェースを利用するように変更

## server.ts を修正
`server/server.ts` を以下の通りに修正する

```typescript:server/server.ts
// アプリケーションサーバ起動設定
import Koa = require('koa');
import path = require('path');
import koaStatic = require('koa-static');
import bodyparser = require('koa-bodyparser');
import MiddlewareRouter = require('./middleware.router');

const app = new Koa();

// サーバ側ミドルウェアの提供
app.use(bodyparser({
    extendTypes: {
        json: ['application/x-javascript']
    }
}))
.use(new MiddlewareRouter().routes());

// クライアント側ミドルウェアの提供
app.use(koaStatic(path.resolve(process.cwd(), './dist/client')));

// サービス開始
const port = 3000;
app.listen(port, () => console.log(`App is listing port in ${port}`));

```
> クライアント側ミドルウェアの提供処理を追加

# フロントエンド側を対応
## sample.service.ts を修正
`client/app/services/sample.service.ts` を以下の通りに修正する

```typescript:client/app/services/sample.service.ts
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
import 'rxjs/add/operator/toPromise';

import SampleRequest = require('../../../../middle/exchange/sample/sample.request');
import SampleResponse = require('../../../../middle/exchange/sample/sample.response');

@Injectable()
export class SampleService {

  constructor(private http: HttpClient) { }

  /** GET通信 サンプル */
  public async getReq(): Promise<any> {
    // GETリクエスト作成
    const httpHeaders: HttpHeaders = new HttpHeaders({'Content-Type': 'application/json'});
    let httpParams: HttpParams = new HttpParams();
    httpParams = httpParams.append('testKey', 'aaaaa');
    httpParams = httpParams.append('otherKey', 'bbbbb');
    httpParams = httpParams.append('testKey', 'ccccc');

    // GETリクエスト送信
    return await this.http.get<SampleResponse>('/api/get-sample', {
      headers: httpHeaders,
      params: httpParams
    }).toPromise();
  }

  /** POST通信 サンプル */
  public async postReq(): Promise<any> {
    // POSTリクエスト作成
    const httpHeaders: HttpHeaders = new HttpHeaders({'Content-Type': 'application/json'});
    const body = {
      num1: 7,
      num2: 9
    } as SampleRequest;

    // POSTリクエスト送信
    return await this.http.post<SampleResponse>('/api/post-sample', body, {
      headers: httpHeaders
    }).toPromise();
  }
}

```
> GET/POSTリクエスト通信を非同期で行うように変更

## コンポーネント修正
### sample.module.ts 修正
`client/app/components/sample.module.ts` を以下の通りに修正する

```typescript:client/app/components/sample.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
import { FormsModule } from '@angular/forms';

import { SampleComponent } from './sample.component';
import { SampleServiceModule } from '../../services/sample/sample-service.module';

/**
 * サンプル コンポーネントモジュール
 */
@NgModule({
    imports: [
        CommonModule,
        RouterModule,
        FormsModule,
        SampleServiceModule
    ],
    declarations: [
      SampleComponent,
    ],
    exports: [
      SampleComponent
    ]
})
export class SampleModule {}

```
> 修正したサービスのモジュールをインポート

### sample.component.ts 修正
`client/app/components/sample.component.ts` を以下の通りに修正する

```typescript:client/app/components/sample.component.ts
import { async } from '@angular/core/testing';
import { Component, OnInit } from '@angular/core';

import { SampleService } from '../../services/sample/sample.service';

@Component({
  selector: 'app-sample',
  templateUrl: './sample.component.html',
  styleUrls: ['./sample.component.css']
})
export class SampleComponent implements OnInit {

  constructor(private service: SampleService) { }

  ngOnInit() {
  }

  /** TEST GET ボタン押下時 */
  async testGetBtn(): Promise<void> {
    const res = await this.service.getReq();
    console.log('\n=== res ===');
    console.log(res);
  }

  /** TEST POST ボタン押下時 */
  async testPostBtn(): Promise<void> {
    const res = await this.service.postReq();
    console.log('\n=== res ===');
    console.log(res);
  }
}

```
> 作成したGET/POSTリクエストのサービスを呼ぶように変更

### sample.component.html 修正
`client/app/components/sample.component.html ` を以下の通りに修正する

```html:client/app/components/sample.component.html
<div>
  sample works!
  <button (click)="testGetBtn()">TEST GET</button>
  <button (click)="testPostBtn()">TEST POST</button>
</div>

```
> GET/POSTリクエストのサービスを呼ぶボタンを追加

# クラサバ通信確認
## 動作確認 (ts-node起動)
* `npm run start` でサーバ（server.ts）を起動し、コンソール上に「`App is listing port in 3000`」と表示されたら、Webブラウザで `http://localhost:3000/#/sample-page` にアクセスし、Webブラウザの開発者モード（ChromeならF12など）を起動し、ブラウザコンソールを表示する。
 * 画面最下部の「TEST GET」ボタンを押下し、ブラウザコンソールに「`{status: true, statusCode: 200, result: 1}`」と表示され、かつ、コンソール上に以下のとおりにレスポンスが表示されたら成功。

```bash
=== SampleContoroller # test ===
{ testKey: [ 'aaaaa', 'ccccc' ], otherKey: 'bbbbb' }
```

 * 画面最下部の「TEST POST」ボタンを押下し、ブラウザコンソールに「`{status: true, statusCode: 200, result: 1}`」と表示され、かつ、コンソール上に以下のとおりにレスポンスが表示されたら成功。

```bash
=== SampleContoroller # test ===
{ num1: 7, num2: 9 }
```

* 動作確認できたら `Ctrl + C` でサーバ終了。

## 動作確認 (node起動)
* 事前に「`dist`」フォルダを削除し、コンソール上で `npm run build` コマンドでビルドを実行し、dist フォルダ配下に「client」「middle」「server」フォルダと配下のファイルが生成されていたら準備完了。
* `node dist/server/server.js` でコンパイルされたサーバ（server.ts）を起動し、コンソール上に「`App is listing port in 3000`」と表示されたら、Webブラウザで `http://localhost:3000/#/sample-page` にアクセスし、Webブラウザの開発者モード（ChromeならF12など）を起動し、ブラウザコンソールを表示する。
 * 画面最下部の「TEST GET」ボタンを押下し、ブラウザコンソールに「`{status: true, statusCode: 200, result: 1}`」と表示され、かつ、コンソール上に以下のとおりにレスポンスが表示されたら成功。

```bash
=== SampleContoroller # test ===
{ testKey: [ 'aaaaa', 'ccccc' ], otherKey: 'bbbbb' }
```

 * 画面最下部の「TEST POST」ボタンを押下し、ブラウザコンソールに「`{status: true, statusCode: 200, result: 1}`」と表示され、かつ、コンソール上に以下のとおりにレスポンスが表示されたら成功。

```bash
=== SampleContoroller # test ===
{ num1: 7, num2: 9 }
```

* 動作確認できたら `Ctrl + C` でサーバ終了。

---

ここまでが クラサバ間の通信部分を構築。
これで、Webアプリとして最低限の動作が可能なので、どっかのサーバにおいてWebブラウザから叩くと使えます。