Angular2で高速SPA開発(2)

はじめに

この記事はAngular2で高速SPA開発(1)の続きとなります。

前回は Angular2 + @angular/cli でアプリを作ってハイ終わり!でしたが、今回はバックエンド、データベースとの連携まで行きたいと思います。

大雑把に言うと、今回のテーマはMEANのAをAngular2で置き換える!ということになります。

MEANとは

MEAN(MEANスタックとも呼ばれる)とはWebアプリケーションの実行環境のことです。Webアプリの現場でよく使われている(と思われる)Linux + Apache + MySQL (MariaDB) + PHP/Perl/PythonであるところのLAMP環境と雰囲気的にはよく似ていて、MongoDB(データベース) + Express.js(バックエンドフレームワーク) + AngularJS(フロントエンドフレームワーク) + Node.js(サーバサイド実行環境)で構成された環境のことです。

アプリケーションの大部分をJavaScriptおよびJSONで記述することができること、サーバを担うNode.jsが省メモリ・高速で動作することからサーバへの不可が小さいこと、などの特徴を持ちます。

各構成要素の詳細については割愛しましょう。

今回のテーマにもあるMEANスタックのAとはもちろんAngularJSを指します。

やりたいこと

ところでこのAngularJSですが、(前回の記事でも述べましたが)現在ではより新しいバージョンのAngular(旧称Angular2。以下単にAngularといいます。)が登場しています。しかしながらこのAngular、AngularJSとの互換性がなく、アーキテクチャも一新されてしまっています。おまけにJavaScriptではなく、いわゆるaltJSのひとつであるTypeScriptが使われます。というような事情もあり、MEANで構成、というとAngularJSが使われている、ような気がしますよね。しかしJavaScriptはちょっとなぁ……というわたしのような人間も(多数)おられるでしょう。

というわけで、こんかいは、MEANのAをAngularに挿げ替え(ついでにサーバサイドもTypeScriptのコードに置き換え)ていきたいと思います!

……と思ったのですが、すでにいい記事がかかれています。

MEANスタック入門(1) MEANスタックとは | tadajam’s blog

この記事でも大いに参考にさせていただきました。

大まかにはこれで問題ないと思いますが、一部ハマったので備忘録も兼ねて書いておきます。

今回の環境

  • MacBook Pro (Retina, 13-inch, Early 2015)
    • macOS Sierra 10.12.5
    • プロセッサ: 3.1 GHz Intel Core i7
    • メモリ: 16 GB 1867 MHz DDR3
  • Node.js v7.5.0
  • npm 5.0.3
  • @angular/core 4.2.4
  • @angular/cli 1.1.3(その他のライブラリは省略)

といういつもどおりのものです。

ではさっそくはじめましょう!

フロントエンド

はい。フロントエンドですが、前回と同じく Angular + @angular/cli を使います。コマンド一発で終わりです。今回は meanapp という名前で作りました。

--routing=true を指定すると、生成時にルーティングの設定ファイルを app-routing.module.ts として切り出してくれます。小規模な開発ならデフォルトのままでもよいのですが、どうせあとから切りたくなりますのではじめからそうしておきましょう。

データベース

データベースはMEANのMMongoDBを使います。MongoDBはいわゆるNoSQLタイプのデータベースで、中でもドキュメント指向と言われる種類のものです。すごく簡単に言うと、データとしてJSONをそのまま放り込むことができます。スキーマがきっちり決まっていなくてもそれほど問題にならないので、とくに開発段階では楽でよいですよね。

MongoDBのインストール、制御に関しては割愛します。macOSで作業しているなら下記の記事を参考にどうぞ。とはいえ、インストールはともかく、起動などはスクリプトで自動化しちゃうので気にしなくてもいいです。

Mac に MongoDBをインストール | Qiita

インストールされたDBにデータを投入しておきます。MongoDBにアクセスし

データベースとデータを作成します。ここでは testdb という名前のデータベースにしました。また > はMongoDBのプロンプトを表しています。

JavaScriptがかけるので、データの用意も簡単ですね。念のため確認しておきましょう。

よさそうですね。

バックエンド

バックエンドはMEANのEExpress.jsを使います。わたしはJavaScriptを書きたくないので、先に述べたとおりTypeScriptで書き、それをトランスパイルして実行しようと思います。

まずはトランスパイラのインストールですね。

とするとインストールされます。

つぎは本体です。リクエストを処理するためのライブラリも一緒にインストールします。

MongoDBのドライバとしてmongooseを使いますのであわせてインストールします。

それから、このような構造になるようにディレクトリを作ります。

server/config.ts にサーバの設定を書きます。

そしてサーバサイドのエントリポイント、 server/bin/www.ts を作成します。

待受開始時にmongooseへ接続しています。

このファイルで参照している app.ts ですが、ここにはルーティングの設定を書きます。といってもここでは振り分けをしているだけで、実際の処理は server/routes 以下のファイルが受け持ちます。

サーバルート / へのアクセスは client/index.html に、 /api/users へのアクセスは usersRouter に振り分けるようにしています。

さて、バックエンドはAPIサーバとして動作することになりますが、そのAPIの本体とも言える処理を routes/users.tsが受け持ちます。

/api/users にアクセスがあったとき、DBからユーザ情報を find してレスポンスとして返していることがわかりますね(モデル models/users.model.ts は省略します)。

こうしてできあがったサーバサイドのコードをトランスパイルするための設定ファイルをserver/tsconfig.jsonに書いていきます。

トランスパイルの方法は、プロジェクトのルートディレクトリで

とするだけです( tsc へのパスは適宜調整してください。グローバルへのインストールであればそのまま tsc 、プロジェクトローカルであれば node_modules/.bin/tsc です。ここへ PATH を通すのもよいかもしれません)。

その後、

とすることでサーバが起動します。ただし、この状態では dist/client 以下にファイルが存在しないので / へのアクセスは404エラーとなります( ng serve などとして簡易サーバを使う場合、実際のファイルは出力されずオンメモリで動作します)。

APIへのアクセスは正しく動作することを確認しておきましょう。 localhost:4300/api/users へとアクセスします。

APIから取得したデータ
APIから取得したデータ

よさそうですね。

AngularとExpress

それではいよいよAngularとExpressを連携させたいと思います。

となるようにディレクトリを作成します。

AngularではAPIへのアクセスはServicが担当することになっていますので、まずはusers APIを扱うServiceを作ります。

として CallExpressApiService を生成します(gはgenerateの略。部品を生成してくれます)。 call-express-api.service.ts を次のように編集します。

このクラスは getUsers() によりAPIからデータを取得します。

続いてこのServiceを利用するComponentを作成します。同じく ng g を使って、

として CallExpressApiComponent を生成し、 call-express-api.component.ts を以下のように編集します(テンプレートは省略)。

そしてこのコンポーネントへのアクセスは app-routing.modules.ts に routes として記述します。

ただし、これを ng serve で動かす場合、クライアントサイドは localhost:4200 、サーバサイドは localhost:4300 で動作することになり、APIサーバへアクセスすることができません(ポートを指定してもオリジンが異なるのでクロスオリジンとなりエラー)。ここでは ng の実行時にプロキシを通し、 /api 以下へのアクセスをサーバサイドに流しましょう。

プロジェクトルートに proxy.conf.json を用意し、

とします。

これで localhost:4200/users へアクセスすると……

クライアント側からAPIにアクセス
クライアント側からAPIにアクセス

はい!できました!!(実際はこのままではうまくいかないと思います。このハマりポイントは後ほど。)

自動化

通常、このようなプロジェクトの開発ではgulpgruntのようなタスクランナを使うことがほとんだと思います。しかし、よくわからないのでここでは package.json に npm-script を書いて対応します。

まずは必要なライブラリをインストールしましょう。

その後、 package.json を

のように編集します。この状態で

すると、サーバ側のスクリプトがビルド(トランスパイル)され、 dist/server 以下に配置され、MongoDBが起動し(なおDBは mongo/data 以下に配置することにしました)、 ng により簡易サーバがプロキシ付きで起動し、そしてサーバ側が nodemon により待受状態になります。 ng serve および tsc -w によりサーバサイド、クライアントサイドともにファイルに変更があった場合は自動でトランスパイルが走ります

本番環境向けにビルドしたい!という場合には

のようなスクリプトを追加し、

とすればよいです。 dist/server 以下にサーバサイドのスクリプトが、 dist/client 以下にクライアントサイドのスクリプトが生成されます。

なお、ビルド後は( ng serve 等しない限りは)サーバ側からも dist/client 以下のファイルが見えるようになりますので

とすることで localhost:4300 にアクセスしてアプリを使うことができます。 ng serve すると、 dist/client 以下はすべて削除され、やはりオンメモリでの動作になるのでサーバ側からは dist/client/index.html 等が参照できません。

 

はい。というわけで、MEANスタックのAをAngularに置き換えてWebアプリが作成できました!!思ったよりも長くなってしまいましたが、コードの記述量はそれほど多くなく、かなり楽なんだなぁという印象ですね。

ここから、わたしがハマったところを解決策付きで解説していきます!

ハマりポイント

HttpModule

トップページから /users にアクセスしてもAPIへのアクセスがされていないように見えます……というかそもそもブラウザのURLを見てもなにも変化がありません。DBもサーバも動いているのになぜでしょうか。

これはモジュールが足りないことによる問題のようです。まず CallExpressApiComponent はたしかに CallExpressApiService を通してAPIを叩くのですが、 HttpModule をインポートしておかないとServiceで利用しているHTTPクライアントが動作しない(?)ようです。結果として404エラーとなります。ここで ng serve の余計なお世話親切な機能により404は / へとリダイレクトされます(この挙動自体はクライアントサイドアプリではスタンダードなようですが)。そのためこのようにURLに変化もなく、正しく表示されない……ということになります。

ですので、 app.module.ts に

を追加し、さらに

としてモジュールをインポートすればOKです。

MongoDBの罠

現在、MongoDBへの接続はこのようになっていると思います。

が、このまま実行すると、

という警告が!!

このメッセージの言うとおりに mongoose.connect(mongoUri, { useMongoClient: true }); としてみると、確かに警告は消えるのですが、なんとモデルデータが取得できなくなってしまいました……(なぜかリストの  だけは表示されてますが)

データが取得できない
データが取得できない

気持ちは悪いですが元のコードに戻しましょう。

どうやらこれはバグのようなので、警告が出るのはあきらめる、バージョンを下げる、修正されるのを待つ、あるいは、パッチを書いてPR送るなどしましょう。

[#5399] DeprecationWarning: open() is deprecated in mongoose >= 4.11.0, use openUri() instead … | Automattic/mongoose | Github

Pug使いたいんですけど

HTMLを書く代わりにPugで生成させたくなってきましたよね?

しかしここまで記事のとおりに来ていると……

まず ng eject が通りません。

メッセージにあるように、 package.jsonstart スクリプトを書いていることが原因です。 eject 時に ng ではなく webpack が直接使われることになる関係上、ここを変更しているとダメなようです……ここでは単純に

とでもしてしまいましょう。

ここであらためて eject ng すると、無事 webpack.config.json が生成されます。

また、 package.json が変更されるので編集しておきます。

あとはAngular2で高速SPA開発(1)で紹介したようにテンプレートを書き換えるとちゃんと動作しますね!

Webpackでもプロキシ

上に書かれているとおりに ng eject し、Webpackでも動作させられるようにはなりました。しかし、今度はプロキシの問題が……Webpackでプロキシを使うには、 webpack.config.js に設定を書く必要があります。

中身は proxy.config.json と同じものです。

これでWebpack経由でもプロキシを通してAPIにアクセスできます。

おわりに

とりあえずフロント、バック、DBの連携はできましたね。あとはもうちょっとこう、アプリケーションぽくなにかできたらいいかなと思いますそしてやっぱりSPAには触れられていません

次回は(あれば)そのあたりも考えてなにか作ってみたいと思います。

お楽しみに。では。