LoginSignup
0
0

More than 5 years have passed since last update.

Angular6 で俺式 KOAN スタック Webアプリを構築する、ほぼ全手順(3)

Last updated at Posted at 2018-12-17

概要

Angular6で「俺式 KOAN スタック」で Web アプリ構築するための個人的な備忘録記事。
概要については「こちら」を参照。
事前に「ほぼ全手順(2)」が完了していることが前提。

今回の内容は「画面のボタンから API を呼べるようにする」コーディングを行う。

フロントエンド対応

Web ブラウザで表示するボタンを作り、そこから複数の内部 API が呼び出せるようにする。

1.ホーム画面用 コンポーネント生成

client/app の配下に 新たにホーム画面となるコンポーネントを作成する。ただし、先にホーム画面用モジュールを作成し、そこにコンポーネントをぶら下げる方式にする。

通常、コンポーネントは、AppModule の declarations に対して追加するが、SPA ではなく複数画面にするつもりなので、別モジュールにした方が後でメンテしやすい。

モジュール作成

# client/app に移動
cd client/app

# modules ディレクトリを作成し、そこに移動
mkdir modules
cd modules

# home モジュール作成
ng generate m home

home というディレクトリが自動生成され、その中に home.module.ts, home.module.spec.ts ができあがる。

コンポーネント作成

# コンポーネント作成 (-m オプションでモジュール指定)
ng generate c home -m home

home ディレクトリ内に home.component.html, home.component.scss, home.component.ts, home.component.spec.ts ができあがる。

home.module.ts には、コンポーネントが自動インポートされる。

2.ホーム画面 コンポーネント修正

ボタン押下処理を追加

client/app/modules/home/home.component.ts
...

export class HomeComponent implements OnInit {

  constructor() { }

  ngOnInit() {
  }

  // メソッド追加
  async testGet() {
    console.log('HomeComponent #testGet 呼出');
  }

  // メソッド追加
  async testPost() {
    console.log('HomeComponent #testPost 呼出');
  }

}

ボタン要素を追加

client/app/modules/home/home.component.html
<p>
  home works!
</p>

<!-- ボタンを追加 -->
<div>
  <button (click)="testGet()">GET TEST</button>
</div>
<div>
  <button (click)="testPost()">POST TEST</button>
</div>

3.画面遷移できるようにする

AppModule にホーム画面モジュール追加

client/app/app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { HomeModule } from './modules/home/home.module'; // 追加

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    HomeModule // 追加
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

画面ルーティング情報にホーム画面コンポーネント追加

path の文字列がURLの末尾に指定されたら、作成したコンポーネントが表示されるように修正。

client/app/app-routing.module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

import { HomeComponent } from './modules/home/home.component'; // 追加

// 修正: const routes: Routes = []; を以下のように変更する
const routes: Routes = [
  {
    path: '',
    redirectTo: 'home',
    pathMatch: 'full'
  },
  {
    path: 'home',
    component: HomeComponent
  }
];

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

App コンポーネント修正

後で表示確認する時に、既存の記載が邪魔なので、必要なもの以外は決しておく。

client/app/app.component.html
<!-- 以下のルーティング記載だけ残して、あとは消す -->
<router-outlet></router-outlet>

4.いったん確認

ローカル環境でアプリ起動

npm run local

Web ブラウザでアクセス

http://localhost:3000 にアクセスし、以下のとおりボタンが表示されていればOK.
この時、ブラウザに搭載の「開発者ツール」などでコンソール表示しておく。

スクリーンショット 2018-11-25 20.27.09.png

殺風景ですが、無事ホーム画面が出ました。。。
URL は自動的に /home にリダイレクトされています。

画面のボタンを押してみる

まず、画面の「GET TEST」ボタンを押した結果(ブラウザ コンソール)

スクリーンショット 2018-11-25 20.28.56.png

続けて、画面の「POST TEST」ボタンを押した結果(ブラウザ コンソール)

スクリーンショット 2018-11-25 20.29.12.png

こんな風に処理に埋め込んだコンソールが呼ばれていればOK.

5.HTTP 通信サービス生成

画面のボタンを押したら、HTTP 通信でサーバサイドにリクエストするサービスを追加する。

サービス作成

モジュール、コンポーネントをコマンド生成した時と同じ階層から、以下のコマンドを実行

# home ディレクトリに移動
cd home

# サービス生成
ng generate s home

home ディレクトリ配下に home.service.ts, home.service.spec.ts ができあがる。

モジュール修正

client/app/modules/home/home.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { HttpClientModule } from '@angular/common/http'; // 追加
import { HomeComponent } from './home.component';
import { HomeService } from './home.service'; // 追加

@NgModule({
  imports: [
    CommonModule,
    HttpClientModule // 追加
  ],
  declarations: [HomeComponent],
  providers: [HomeService] // 追加
})
export class HomeModule { }

サービスに HTTP 通信処理を追加

home.service.ts を以下のとおりに書き換える。

client/app/modules/home/home.service.ts
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';

@Injectable()
export class HomeService {

  constructor(protected http: HttpClient) { }

  async getTest<Req>(request: Req): Promise<any> {
    let response: any;
    const url = '/api/sample';

    try {
      // GET リクエスト生成
      let httpParams: HttpParams = new HttpParams();
      const keys = Object.keys(request);
      for (const key of keys) {
        httpParams = httpParams.append(key, request[key]);
      }

      response = await this.http.get<any>(url, {
        headers: new HttpHeaders({ 'Content-Type': 'application/json' }),
        params: httpParams
      }).toPromise();
    } catch (error) {
      response = {
        val: 'GET リクエストでエラーが発生しました。',
        error: error
      };
    }

    return response;
  }

  async postTest<Req>(request: Req): Promise<any> {
    let response: any;
    const url = '/api/sample';

    try {
      response = await this.http.post<any>(url, request, {
        headers: new HttpHeaders({ 'Content-Type': 'application/json' }),
      }).toPromise();
    } catch (error) {
      response = {
        val: 'POST リクエストでエラーが発生しました。',
        error: error
      };
    }

    return response;
  }
}

コンポーネント修正

サービスの HTTP 処理を呼び出すように修正する

home.component.ts を以下のとおりに書き換える。

client/app/modules/home/home.component.ts
import { Component, OnInit } from '@angular/core';

import { HomeService } from './home.service';

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

  constructor(private service: HomeService) { }

  ngOnInit() {
  }

  async testGet() {
    console.log('HomeComponent #testGet 呼出');

    const request = {testKey: 'aaa', otherKey: 'bbb'};
    const response: any = await this.service.getTest(request);
    console.log('[response]\n', response);
  }

  async testPost() {
    console.log('HomeComponent #testPost 呼出');

    const request = {testKey: 'ccc', otherKey: 'ddd'};
    const response: any = await this.service.getTest(request);
    console.log('[response]\n', response);
  }

}

バックエンド作らないと動作確認ができないので、一旦これで、画面側の HTTP 通信ロジックは完了。

バックエンド対応

複数の内部 API を、画面のボタンに合わせて呼び出せるようにする。

1.バックエンドのコントローラを作成

server ディレクトリ配下に controllers というディレクトリを作成し、
その配下に以下のとおりに sample.controller.ts というファイルを作る。

server/controllers/sample.controller.ts
/** サンプル コントローラ */
export class SampleContoroller {

  public async test(request: any): Promise<any> {

    // リクエスト内容にコメントを追加しただけの JSON レスポンス
    return {
      statusCode: 200,
      body: { result: `Your Request is\n${JSON.stringify(request)}` }
    };
  }

}

2.API のパスを管理するクラスを作成

server ディレクトリ配下に managers というディレクトリを作成し、
その配下に以下のとおりに api-path.manager.ts というファイルを作る。

server/managers/api-path.manager.ts
import { SampleContoroller } from '../controllers/sample.controller';

/** API パス インターフェース */
interface IApiInfo {
  /** HTTP メソッド */
  method: string;
  /** API パス */
  path: string;
  /** 実行処理 */
  process: (request: any) => Promise<any>;
}

/**
 * API パス 管理クラス
 *
 * HTTP メソッド、API パス、バックエンド コントローラの関係を管理する)
 */
export class ApiPathManager {

  public static readonly apis: IApiInfo[] = [
    { method: 'GET', path: '/api/sample', process: new SampleContoroller().test },
    { method: 'POST', path: '/api/sample', process: new SampleContoroller().test },
  ];

}

3.API のルーティングを管理するクラスを作成

server/managers ディレクトリ配下に、以下のとおりに middleware-router.manager.ts というファイルを作る。

server/managers/middleware-router.manager.ts
import * as KoaRouter from 'koa-router';

import { ApiPathManager } from './api-path.manager';

/**
 * ミドルウェア ルータ 管理クラス
 *
 * ミドルウェアのルーティングを設定し, APIを提供する
 */
export class MiddlewareRouterManager {

  public routes(): KoaRouter.IMiddleware {
    const koaRouter = new KoaRouter();

    // 各 API に対応したミドルウェアを提供
    for (const api of ApiPathManager.apis) {
      if (api.method === 'GET') {
        koaRouter.get(api.path, this.controller(api.process));
      } else if (api.method === 'POST') {
        koaRouter.post(api.path, this.controller(api.process));
      } else if (api.method === 'PUT') {
        koaRouter.put(api.path, this.controller(api.process));
      } else {
        koaRouter.delete(api.path, this.controller(api.process));
      }
    }

    return koaRouter.routes();
  }

  /** コントローラ呼出 */
  private controller(controll: (request: any) => Promise<any>) {

    return async function (ctx: KoaRouter.IRouterContext) {
      // GET, DELETE リクエストの時は、クエリから要求データを生成. それ以外の場合は、ボディから要求データを生成
      const ctxRequest = ctx.request;
      const request = (ctxRequest.method === 'GET' || ctxRequest.method === 'DELETE')
        ? ctxRequest.query : ctxRequest.body;

      // コントローラの結果を返却
      const response = await controll(request);
      ctx.status = response.statusCode;
      ctx.body = response.body;
    };
  }

}

先ほど作った api-path.manager.ts の情報を利用して、サービスから投げられる HTTP メソッドと URL を判定し、紐づく各コントローラに分岐するようなロジック。
GET や POST の HTTP メソッドによって、リクエストの取得元が異なる。

4.web-server.ts を修正

今作った middleware-router.manager.ts をインポートし、そこからルーティングされたバックエンドを、 app.use() を使ってアプリに提供するように変更する。
また、このファイルでは koa-router を使わなくなったので、インポートから消す。

server/web-server.ts
import * as Koa from 'koa';
import * as koaStatic from 'koa-static';
// import * as KoaRouter from 'koa-router'; // この行を削除
import * as bodyParser from 'koa-bodyparser';
import * as config from 'config';
import { join } from 'path';
import { MiddlewareRouterManager } from './managers/middleware-router.manager'; // 追加

...

app.use(bodyParser({
  // application/x-javascript 型のリクエストを JSON として処理できるようにする
  extendTypes: {
    json: ['application/x-javascript']
  }
}));

// ***** ↓↓ 削除 ↓↓ *****
// サーバサイド側 ミドルウェア ルーティングを定義
// const koaRouter = new KoaRouter();
// koaRouter.get('/api/*', (ctx) => {
//   ctx.status = 200;
//   ctx.body = { test: 'API が呼ばれました' };
// });
// ***** ↑↑ 削除 ↑↑ *****

// ***** ↓↓ 追加 ↓↓ *****
// サーバサイド側 ミドルウェアの提供
app.use(new MiddlewareRouterManager().routes());
// ***** ↑↑ 追加 ↑↑ *****

5.確認

アプリ起動
npm run local

アプリ起動したら、Web ブラウザから http://localhost:3000 にアクセス。
「開発者ツール」を開きコンソール表示しておく。

スクリーンショット 2018-11-25 20.27.09.png

ブラウザから API アクセス

画面の「GET TEST」ボタンを押した結果(ブラウザ コンソール)
GET リクエストのサービスが正常に呼ばれ、サンプルコントローラを通過してレスポンスが返却された。

test_get_01.png

続けて、画面の「POST TEST」ボタンを押した結果(ブラウザ コンソール)
POST リクエストのサービスが正常に呼ばれ、サンプルコントローラを通過してレスポンスが返却された。

test_post_01.png

結果

これで、API を画面から呼び出せるようになった。
/api/ に続く、別の API を追加する場合、処理を行うバックエンドを用意した後、server/managers/api-path.manager.ts にコントローラをインポートして、パスと HTTP メソッドを指定するだけで良い。

必要あれば、パスやメソッドを定数にしておくと良い。

この後やること

ロギング機構を作る

→ 「Angular6 で俺式 KOAN スタック Webアプリを構築する、ほぼ全手順(4)」に執筆

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0