はじめに
- Google App Engine をバックエンドサーバーとして動く普通の Angular アプリケーションの作り方を紹介していく。
- 今回は導入編としてアプリケーションのひな形を作って通信ができるだけのところまでやってみる。
準備
-
Go, Node.js のインストール
-
Google Cloud SDK のインストール
- GAE のローカル開発サーバーなどが入っているので公式ドキュメントの通りにインストールする
-
Angular CLI のインストール
- Angular CLI を使って Angular プロジェクトの初期化や管理ができるのでREADME.mdに従ってインストールする
プロジェクトルートの作成
- $GOPATH の通っているディレクトリにプロジェクトルートを作成する
- 以下は github.com で公開し、ユーザー名が you で、プロジェクト名が gae-angular-sample の場合のプロジェクトルートディレクトリの作成
mkdir -p $GOPATH/src/github.com/you/gae-angular-sample/{server,client}
GAE アプリケーションのひな形作成
-
GAE アプリのひな形としては、HTTP リクエストを処理するメインの go ファイルと、設定用の yaml が必要になる
-
メインの Go ファイルとして "/" へのリクエストで "hello world" と出力するだけの HTTP ハンドラの定義された
server/gae/app.go
を作成する
server/gae/app.go
package main
import (
"net/http"
"fmt"
)
func init() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "hello world")
})
}
- 設定用の Yaml として全リクエストを前記の Go ファイルで処理するように定義した
server/gae/app.yaml
を作成する
server/gae/app.yaml
runtime: go
api_version: go1.8
handlers:
- url: /.*
script: _go_app
開発サーバーを起動する
- Cloud SDK に入っている dev_appserver.py コマンドで開発サーバーを立ち上げる
cd server/gae
dev_appserver.py app.yaml
- 起動ログに表示される通り
http://localhost:8080
で立ち上げたアプリケーションへアクセスできる - これも起動ログにあるが
http://localhost:8000
で管理用のサーバーにアクセスすることができ、開発用にエミュレートされた Google Datastore などを操作することができる
Angular アプリケーションのひな形の作成
- Angular CLI を使ってひな形を作成する
# プロジェクトルートヘ
cd $PROJECT_ROOT
# client という名前でひな形の作成
ng new client
# 依存パッケージのインストール
cd client && npm install
Angular アプリの開発サーバーを起動する
npm start
-
http://localhost:4200
でクライアントの開発サーバーへアクセス
GAE アプリと Angular アプリを同一ホストで提供する
- GAE と Angular でそれぞれ開発サーバーを立ち上げると、それぞれ別ドメインとして動作してしまい Cookie などを共有することができずに面倒な場合がある
- ここでは Angular アプリのソースをビルドして GAE アプリで提供することで同一ドメインで動作させる
Angular アプリのビルド
- 下記の通り
client/package.json
に GAE アプリ用のビルドスクリプトを追加する
client/package.json
{
...
"scripts": {
...
"build:gae": "ng build --prod --output-path=../server/gae/client/ --base-href=/client/"
- ビルドスクリプトを実行して
server/gae/client
にビルド後のソースがコピーされていることを確認する
npm run build:gae
GAE アプリで Angular アプリのファイルを提供する
- GAE アプリの設定ファイルを変更してビルドした Angular アプリのファイルを静的ファイルとして提供する
- 以下のように "handlers" 要素に "/client/" 以下の静的ファイルを提供する設定と、それ以外の "/client/.*" で "/client/index.html" を提供する設定を追加する
server/gae/app.yaml
runtime: go
api_version: go1.8
handlers:
- url: /client/(.*\.(gif|png|jpeg|jpg|css|js|ico))$
static_files: client/\1
upload: client/(.*)
- url: /client/(.*)
static_files: client/index.html
upload: client/index.html
- url: /.*
script: _go_app
- GAE 開発サーバーを起動し
http://localhost:8080/client/
で Angular アプリが立ち上がるのを確認する
自動でリビルドする
- ビルドコマンドにオプションを足すとファイル変更を検知してリビルドしてくれる
npm run build:gae -- --watch
Angular アプリから GAE アプリへ通信する
GAE アプリに通信の受け口を追加する
- パラメータを受けて動的な結果を返す HTTP ハンドラを定義する。
- パラメータの bind と JSON のレスポンスなどウェブサーバーではおなじみの処理を手軽に実装したいのでウェブアプリケーションフレームワーク echo を利用する
server/gae/app.go
package main
import (
"github.com/labstack/echo"
"net/http"
)
func init() {
// 軽量なウェブアプリケーションフレームワーク echo を使う
// 素の net/http に比べてパラメータの bind や Json の出力を便利になる
e := echo.New()
// ルート定義
e.GET("/hello", helloHandler)
// 全リクエストを echo で処理する
http.Handle("/", e)
}
// helloHandler のリクエスト
type helloRequest struct {
// クエリストリングの name 要素を受け取る
Name string `query:"name"`
}
// helloHandler のレスポンス
type helloResponse struct {
Message string
}
// echo のハンドラ型を定義
func helloHandler(c echo.Context) error {
// 入力を受け取って構造体に入れる
req := new(helloRequest)
c.Bind(req)
// ステータスコード 200 で JSON を返す
return c.JSON(http.StatusOK, helloResponse{
Message: "hello " + req.Name,
})
}
- 追加したライブラリをインストールして開発サーバーを起動する
go get github.com/labstack/echo
cd server/gae
dev_appserver.py app.yaml
Angular アプリから GAE アプリに通信する
-
Angular で HTTP 通信をする場合は @angular/common/http の HttpClient を利用する
-
HttpClient を DI できるように AppModule に HttpClientModule を追加する
client/src/app/app.module.ts
import { AppComponent } from './app.component';
+import {HttpClientModule} from "@angular/common/http";
@NgModule({
declarations: [
AppComponent
],
imports: [
+ HttpClientModule,
BrowserModule
],
- AppComponent 初期化時に通信を行う
- コンストラクタで HttpClient を DI で初期化し GET リクエストを送信、console.log で結果を表示する
- レスポンスの型 helloResponse は事前に定義する
client/src/app/app.module.ts
interface helloResonse {
Message: string
}
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'app';
constructor(private http: HttpClient) {
this.http.get<helloResonse>("/hello?name=bob").subscribe(
res => console.log(res)
);
}
}
おわりに
- 実コードはリポジトリを参照のこと