MEANスタック入門(4) AngularとExpressの連携

今回はExpressで作成したAPIをAngularから実際に利用しつつ、Angularの基本的な開発方法に触れていきます。

project-name/src/app配下で作業し、作業後の構成は以下のようになります。

app
│  app-routing.module.ts
│  app.component.html
│  app.component.scss
│  app.component.spec.ts
│  app.component.ts
│  app.module.ts
│
├─components
│  └─call-express-api
│          call-express-api.component.html
│          call-express-api.component.scss
│          call-express-api.component.spec.ts
│          call-express-api.component.ts
│
└─services
        call-express-api.service.spec.ts
        call-express-api.service.ts

Serviceの作成

Angularでは、各種APIへのアクセスはServiceから行います。

servicesディレクトリを作成し、Angular CLIng g serviceコマンドで必要ファイルを生成します。 ng g serviceコマンドはカレントディレクトリに依存するので、servicesディレクトリまで移動してから実行します。

$ ng g service callExpressApi

実行すると以下の2ファイルが生成されます。

  • call-express-api.service.ts
  • call-express-api.service.spec.ts

specのほうはテスト用なので、call-express-api.service.tsのほうにAPIにアクセスするgetUsersメソッドを実装します。

まず、HTTPアクセスに必要なHttpクラスをインポートしてコンストラクタの引数に指定します。 また、Http.getはObservable型の値を返すので、Observable.mapを使うためにrxjs/add/operator/mapをインポートします。

call-express-api.service.ts

import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import 'rxjs/add/operator/map';

@Injectable()
export class CallExpressApiService {

  constructor(
    private http: Http
  ) { }

  getUsers() {
    return this.http.get("api/users")
            .map(response => response.json());
  }
}

上記ではapi/usersにアクセスしていますが、ng serverで実行している場合、Angularの簡易サーバとAPIサーバが別のポートで動いているので、Angular側からAPIを呼ぶと404のエラーになります。 また、ポートまで指定してAPIにアクセスした場合も、クロスオリジンのエラーで同じく実行出来ません。

これを回避するには、プロジェクトのルートディレクトリにproxy.conf.jsonを作成し、ng server実行時に--proxy-configオプションで読み込ませます。

proxy.conf.json

{
  "/api": {
    "target": "http://localhost:4300",
    "secure": false
  }
}
$ ng server --proxy-config proxy.conf.json

これでポートの差異を意識せず開発出来るようになります。

Componentの作成

Componentはビューの部品のようなもので、各部品毎の表示や動作を定義します。

componentsディレクトリを作成し、Angular CLIng g componentコマンドで必要ファイルを生成します。

ng g componentコマンドはng g serviceと同じくカレントディレクトリに依存するので、componentsディレクトリまで移動してから実行します。

$ ng g component callExpressApi

実行するとcall-express-apiディレクトリが生成され、その配下に以下4ファイルが生成されます。

  • call-express-api.component.html
  • call-express-api.component.scss
  • call-express-api.component.ts
  • call-express-api.component.spec.ts
call-express-api.component.ts

CallExpressApiComponentクラスを作成し、先ほど作成したCallExpressApiServiceを実行して、HTML表示用の変数に代入します。

CallExpressApiServiceをインポートしてコンストラクタの引数に指定し、Serviceのインスタンスを生成するためにprovidersで読み込みます。これは、app.module.tsでグローバルに読み込んでも動きますが、今回はこのComponentだけなのでここで読み込みます。

getUsersはObservableを返すので、subscribeメソッドでデータを受け取って変数usersに代入します。

call-express-api.component.ts

import { Component, OnInit } from '@angular/core';
import { CallExpressApiService } from '../../services/call-express-api.service';

@Component({
  selector: 'app-call-express-api',
  templateUrl: './call-express-api.component.html',
  styleUrls: ['./call-express-api.component.scss'],
  providers: [CallExpressApiService]
})
export class CallExpressApiComponent implements OnInit {

  users: any;

  constructor(
    private callExpressApiService: CallExpressApiService
  ) { }

  ngOnInit() {
    this.callExpressApiService.getUsers().subscribe(u => {
      this.users = u.users;
    });
  }
}
call-express-api.component.html

APIからの返答が代入された変数usersを利用して、内容を表示するHTMLを作成します。

配列を受けて要素の数だけ繰り返し表示させる場合、ngForディレクティブを利用します。*プレフィックスを付与することでtr要素がテンプレート化され、そのテンプレートを元にusersの各要素の内容が表示されます。

call-express-api.component.html

<table border="1">
  <tr *ngFor="let user of users">
    <td>{{user.id}}</td>
    <td>{{user.address}}</td>
  </tr>
</table>

ルーティングの設定

初めに表示されるComponentはapp直下にあるapp.component.htmlです。 ここにナビゲーションを用意し、ナビゲーションをクリックするとヘッダーを残してコンテンツ部分が切り替わるような、いわゆるシングルページアプリケーションの構成にします。

routerLinkディレクティブでURIを指定し、<router-outlet>タグでコンテンツ表示位置を設定します。

app.component.html

<header>
  <h1 class="logo"><a href="/">MEAN</a></h1>
  <nav>
    <ul>
      <li><a routerLink="/users">Users</a></li>
    </ul>
  </nav>
</header>
<router-outlet></router-outlet>

Angularアプリをng newで初めに生成する際、--routing=trueを付けているとapp直下にapp-routing.module.tsが生成され、app.module.tsで既に読み込まれています。

このapp-routing.module.tsに、ルーティングさせたいURIとComponentを記述します。

app-routing.module.ts

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

import { CallExpressApiComponent } from './components/call-express-api/call-express-api.component';

const routes: Routes = [
  {path: 'users', component: CallExpressApiComponent},
];

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

動作確認

ng serverで実行している場合のAngular側のテストは、http://localhost:4200にアクセスします。 f:id:tadajam:20170504094858p:plain

Usersのリンクをクリックすると、APIのレスポンスを受けてテーブルが表示されます。 f:id:tadajam:20170504095029p:plain

ビルド後はdistディレクトリのclient配下のファイルも揃うので、dist/server/bin/www.jsを実行するだけでhttp://localhost:4300からclient配下のファイルも提供されます。