AngularJSアプリケーション開発ガイド 第3章 AngularJSアプリケーションの開発

AngularJSアプリケーション開発ガイド

開発ツール(IDE)

WebStormがおすすめ(PhpStormはWebStormを内蔵しているので、PHP開発者はPhpStormにするとお得)。

  • コード補完、構文エラー等の表示、ライブラリ・フレームワーク対応、デバッガ統合
  • AngularJSプラグインがある

アプリケーションの実行

Yeomanを使う場合

「yeoman server」コマンドでWebサーバが起動でき、ライブリロードも実行される。
※本書執筆時点では、yeomanは「yeoman」コマンドで実行していたが、Yeoman 1.0.0以降ではyeomanコマンドは無くなっている

Yeomanを使わない場合

自前でWebサーバを立ち上げる必要がある。本文中ではnodeで簡易的なサーバを書く方法と、pythonでサーバを立てる方法が紹介されている。

PHPがインストールされているPC(最近のOS XにはPHPが同梱されている)なら、「php -S」で、カレントディレクトリをドキュメントルートにしてWebサーバを立ち上げることができる。localhostでポートが808番なら、「php -S localhost:8080」でサーバを立てて、http://localhost:8080/でアクセスできる。

AngularJSアプリケーションのテスト

テストランナーはKarmaがおすすめ。複数のブラウザ上で高速にテストが実行できる。

ユニットテスト

AngularJSはデフォルトではJasmineスタイルのテストに対応している。以下がコード例。

エンドツーエンドテスト

AngularJSにはシナリオランナーという仕組みが用意されている。これを使うと、ユーザーの操作をシミュレートできる。

シナリオランナーでは、Jasmineと同様の構文を使ってアプリケーションのふるまいを記述できる。同様のテスティングフレームワークとしてはSeleniumなどがあるが、AngularJS内臓のシナリオランナーは、さまざまな点でAngularJSにより適した機能をもっている。

コードのコンパイル(minify)

Closure Compiler等が利用できる。AngularJSで開発する上で特に注意が必要なのは、依存性注入の書き方について。

minifyツールは、変数をminifyすることはあっても、文字列をminifyすることはない、という性質を利用している。

minifyを絶対に使用しないならminifyすると動かない書き方でもよいが、そうでないならminifyしても動く書き方をすべき。

デバッグのコツ

  • デバッグを行う場合には必ず、最小化されていないバージョンのソースコードや依存先ライブラリを使用する
  • ソースコードはHTMLの中に埋め込まず、独立したJavaScriptファイルに記述する
  • ブレークポイントを利用する
  • ブラウザの「すべての例外で停止」オプションを利用してみる

Batarang

BatarangというChrome拡張を使用すると、AngularJSアプリケーションの内部状態等を可視化できる。

Yeoman(ワークフローの最適化)

Yeoman関係は本書執筆時点と現在では大きく変わっている。Yeomanは現在ではscaffoldingにのみ使用され、依存関係の解決はBower、タスクの実行はGruntGulpが担当する。

以下は、現時点(yeoman 1.3.3)での使い方。
※2015/04/29 generator-karmaが無いとWARNINGが出るのでgenerator-karmaを明示的にインストールするよう修正。環境:node.js v0.12.2、npm v2.7.4、yoeman v1.4.6、grunt-cli v0.1.13、grunt v0.4.5

Yeomanのインストール

AngularJSプロジェクトの新規作成

yoeman1.4.2現在、パッケージの依存管理はbowerで行う。yoemanではスキャッフォルドのためのテンプレートをジェネレータと呼んでいる。AngularJS用のジェネレータとして最も広く使われているのはgenerator-angular。また、テストランナーkarmaのためのジェネレータも必要(無くても動くが、WARNINGが出る)。

Sass入れる? とか聞かれるので適当に選択すればOK。angular-routeは入れておいたほうがよい(後述)。

サーバの実行

Gruntを使う場合(yo angularするとGruntfile.jsが自動生成されるので、Gruntを使うのが手軽)。

ルート、ビュー、コントローラの追加

※yo angular:routeを実行するには、yo angularの時点でangular-routeを選択してインストールしておく必要がある。インストールされていない場合は、再度yo angularを実行すればよい。

テスト

プロジェクトのビルド

  • すべてのJavaScriptファイルのコードを連結し、1つのファイルを生成する
  • ファイルのバージョン管理を行う
  • 画像を最適化する
  • アプリケーションキャッシュのためのマニフェストファイルを生成する

AngularJSとRequireJSの統合

RequireJSで依存先の定義と管理を行う。Bowerがパッケージマネージャなのに対して、RequireJSはモジュールごとに使用するJavaScriptを管理する、といった使い方のツール。割とめんどくさそうだけど、規模が大きくなると必要かも。

AngularJSアプリケーション開発ガイド 第2章 AngularJSアプリケーションの構造 その4

AngularJSアプリケーション開発ガイド

フィルタを使ったデータ形式の指定

フィルタを使うと、テンプレートの中で表示されるデータの表示形式を指定できる。フィルタの構文は次のとおり。

ここでexpressionはAngular式を表し、filterNameは適用したいフィルタの名前を表す。フィルタに与えるパラメータはコロンで区切って指定する。パラメータにもAngular式を指定できる。

組み込みのフィルタの1つ、currencyフィルタは次のように利用する。

フィルタの指定はコントローラやモデルではなくビューの中で行う。数値の前のドル記号は人間のユーザーにだけ意味があり、数値を処理するロジックにとっては重要ではない。

AngularJSには日付や数値あるいは大文字への変換といったフィルタも用意されている。

データバインディングの中でパイプ記号(|)を指定すると、複数のフィルタを連鎖させることができる。

上の例では、numberフィルタに四捨五入する桁数を指定し、12.9を13に変換している。

組み込みのフィルタだけでなく、自分で作成したフィルタを適用することもできる。フィルタの実装例は以下。

このフィルタは、以下のようにして呼び出せる。

サーバとの通信

サーバとの通信のため、$httpサービスが用意されている。単純なHTTPの他、JSONPとCORSにも対応し、JSONの脆弱性やXSRFに対する防御の機能も含まれている。リクエストやレスポンスのデータへの変換や、シンプルなキャッシュも可能。

サーバに問い合わせを行うコードの例は以下。

ディレクティブを使ったDOMの更新

ディレクティブは、HTMLの構文に対する拡張。独自の要素や属性を通じて、ふるまいやDOMへの変換を指定できる。再利用可能なUIコンポーネントを定義したりアプリケーションの設定を行ったりするほか、UIのテンプレートの中ではほぼどんな処理でも可能。AngularJSに組み込みで用意されているディレクティブだけでなく、自分でディレクティブを定義することもできる。

サービスと同様、ディレクティブもモジュールのAPIを通じて定義する。

入力データの検証

AngularJSアプリケーションでの<form>要素は自動的に機能拡張され、SPA(Single Page Application)に適した機能が追加される。

HTML5のrequired属性やtype=”email”、type=”number”などを使用すると、対応ブラウザではブラウザのネイティブ機能が呼び出され、非対応ブラウザでも同じ処理を行うディレクティブが自動的に追加される。
(このように、非対応のブラウザにも同等の機能を追加することをpolyfillと呼ぶ)

フォームのデータの正当性は、$validプロパティで確認できる。フォーム内のすべてのフィールドの値が正当な場合に、$validにtrueがセットされる。

AngularJSアプリケーション開発ガイド 第2章 AngularJSアプリケーションの構造 その3(モジュールを使った依存関係の管理)

AngularJSアプリケーション開発ガイド

モジュールを使った依存関係の管理

  • コントローラ:ビューのテンプレートに対して適切なデータと関数を公開する
  • モジュール:アプリケーションの機能ごとに依存関係をグループ化し、その依存先を自動的に取得
    • (依存性の注入、Dipendency Injection = DI)
  • サービス:依存先のこと。シングルトンオブジェクトで、アプリケーションの機能に必要な処理を提供
    • AngularJSが提供するサービスの例:$location, $route, $http

コントローラでサーバと通信するようなコードを書くと、以下のような問題が発生する

  • コントローラ毎に同じような処理が重複する => 保守の手間が増える
  • コントローラの責任の境界が不明確になる => コードが読みにくくなる
  • ユニットテストを行いづらくなる(サーバを利用したテストは遅い、モンキーパッチは複雑で不安定)

モジュールと依存性の注入を適用すれば、この問題が解決できる。

アプリケーションに固有の処理についてはすべて、自分で定義したサービスの中で行うべき。サービスはそれを必要としているすべてのコントローラの間で共有できる。サービスはコントローラ間でのやり取りや内部状態の共有にも適している。

AngularJSに用意されているサービスの名前は$で始まるので、自分で作成するサービスについては競合を避けるために$以外で始まる名前にすべき。

サービスはモジュールのオブジェクトに用意されているAPIを使って定義する。

関数 定義
provider カスタマイズ可能なサービスを、複雑なロジックで生成する
factory カスタマイズ不可能なサービスを、複雑なロジックで生成する
service カスタマイズ不可能なサービスを、単純なロジックで生成する

factory()を使ってItemsサービスを作るコード例は以下。

サービスは、以下のように利用する。

AngularJSはShoppingControllerを生成する際に、$scopeとItemsサービスを渡す。これは、パラメータ名のマッチングを通じて行われる。

サードパーティモジュールの読み込み

angular.module()の第2引数にモジュール名の配列を渡すことで、サードパーティモジュール等の依存関係を指定できる。

AngularJSアプリケーション開発ガイド 第2章 AngularJSアプリケーションの構造 その2

AngularJSアプリケーション開発ガイド

CSSのクラスとスタイル

ng-class・ng-styleで、条件に応じたCSSを適用できる。Angular式が以下のようになる必要がある。

  • クラス名を指定した文字列
  • クラス名の配列
  • クラス名と真偽値を対応付ける連想配列

src属性とhref属性

img要素のsrc属性や、a要素のhref属性を動的に書き換えたい場合、ng-src/ng-hrefを使用する。

Angular式

テンプレートでのAngular式は、テンプレートとアプリケーションロジック、及びデータとを接続し、同時にアプリケーションロジックがテンプレート内に進入するのを防ぐことを意図している。

Angular式では、四則演算、比較、論理演算、ビット演算、$scopeで公開されている関数の呼び出し、配列の要素やオブジェクトのプロパティへのアクセスができる。ただし、javaScriptの式と等価ではない(ループ・フロー制御・データの変更を伴う演算などには対応していない)。

コントローラによるUIの責任の分離

アプリケーションの中でコントローラは3つの役割を持っている。

  1. アプリケーションのモデルを初期状態にセットアップする
  2. $scopeを通じ、モデルと関数をビューに公開する
  3. モデルへの変更を監視し、適切に応答する

コントローラの本質的な意義は、ユーザーがビューとのインタラクションを通じて行おうとしていることを実現するという点にある。コントローラを小さくかつ管理の容易なものにするために、ビューの中で機能を持った単位ごとに1つずつコントローラを用意することが推奨されている。

UIの構造が複雑な場合は、コントローラを入れ子状に定義できる。入れ子の内側のコントローラに渡される$scopeは、外側のコントローラの$scopeを継承している。

$scopeを通じたモデルのデータの公開

コントローラに渡される$scopeオブジェクトは、モデルのデータをビューに公開するための仕組みでもある。AngularJSでは、$scopeの中でアクセスできるプロパティだけがモデルのデータとみなされる。

ng-modelで指定されたモデルはコントローラの$scopeの中で解釈される。同時に、ここでは入力フィールドの状態と指定されたモデルとの間で双方向のデータバインディングが定義される。

$watchを使ったモデルの変化への監視

$scope.$watchは、モデルのデータが変化した場合に通知を受け取るために使う。プロパティとしてアクセス可能なものや、関数によって計算可能なものであれば何でも監視できる。$watchのシグネチャは以下のようになっている。

watchFn:
Angular式の文字列か、監視対象の現在の値を返す関数を指定する。これらは何度も実行されるため、実行による副作用がないようにする必要がある。
watchAction:
watchFnで指定した値が変化した場合に、呼び出される関数またはAngular式。
deepWatch:
trueの場合、対象のオブジェクトが持つそれぞれのプロパティについて変更の有無がチェックされる。

$watchは、通知を受け取る必要がなくなった場合に監視を解除するための関数を返す。

以下は、値引き機能付きショッピングカートのサンプル(書籍のサンプルに手を加えていて、angular-locale_jaを追加して、currencyフィルタで円記号が表示されるようにしていたり、for文でtotalを計算していたのをreduceを使ったりしている)。

$watchのパフォーマンス

$watchが頻繁に呼ばれると、パフォーマンス上の問題を引き起こすことがある。

複数の対象の監視

複数のプロパティを監視し、そのいずれかが変化した場合に関数を呼び出すには、以下の2つの方法がある。

  1. プロパティの値を連結したものを監視する
  2. 監視対象を含む配列又はオブジェクトを定義し、deepWatchにtrueを指定してこれを監視する

今回はここまで。次回は「2.4 モジュールを使った依存関係の管理」から。

AngularJSアプリケーション開発ガイド 第6章 ディレクティブ

AngularJSアプリケーション開発ガイド

※1つ前の記事が第2章の途中なので順番前後しますが、業務でカスタムディレクティブの作成が必要になったので、第6章 ディレクティブを先に読みます。

ディレクティブとHTMLの構文規則

AngularJSでは、以下の記法でディレクティブを呼び出せる。

構文規則 記法
(なし) namespace-name ng-repeat=”item in items”
XML namespace:name ng:repeat=”item in items”
HTML5 data-namespace-name data-ng-repeat=”item in items”
XHTML x-namespace-name x-ng-repeat=”item in items”

APIの概要

ディレクティブの設定項目

プロパティ名 目的
restrict テンプレートの中でこのディレクティブがどのように使われるか
priority 同じ要素で指定されているディレクティブの間で、実行の順序を決めるための数値
template インラインのテンプレートを文字列として指定する
templateUrl テンプレートが置かれているURLを指定する
replace trueが指定されている場合、現在の要素が置き換えられる
transclude ディレクティブの子要素よして記述されているコンテンツが、新しいテンプレートの中に移される
scope 親の$scopeを継承せずに、このディレクティブのために新しい$scopeを生成する
controller ディレクティブ間での通信を可能にするためのコントローラを生成する
require このディレクティブが正しく機能するために必要な、他のディレクティブを指定する
link 生成されたDOMを変更したりイベントリスナーを追加したりデータバインディングを定義したりする
compile 繰り返されるディレクティブのテンプレートを変更する

ディレクティブの名前

ディレクティブの名前は以下のように、モジュールのdirective()メソッドを使って指定する。

ディレクティブの名前は自由に指定できるが、名前空間を指定して外部のディレクティブとの競合を防ぐのが一般的。

ディレクティブ定義のオブジェクト

restrictプロパティ

restrictプロパティを使うと、ディレクティブの形態を指定できる。値は複数指定可能(デフォルトはA)。

|値|形態|例|
|E|要素||
|A|属性|

|
|C|クラス|

<

div class=my-menu:Products>|
|M|コメント||

priorityプロパティ

1つの要素に複数のディレクティブが記述されており、これらの実行の順序を指定したい場合には、priorityプロパティを利用する。最も高い値が指定されているディレクティブが最初に実行される。指定がない場合のデフォルト値はゼロ。

なお、AngularJS自身のディレクティブ(ng-repeat等)にもpriorityが指定されており、例えばng-repeatのpriorityは1000

templateプロパティ

要素のコンテンツを指定されたテンプレートで置き換える。
templateUrlを使うと、サーバ上のファイルをテンプレートとして読み込む。

templateプロパティを使ったディレクティブの定義例:

helloTemplate.htmlの中身は以下。

Chromeを使っている場合、同一生成元ポリシーというセキュリティ上の制約のため、テンプレートをfile://で読み込めない。Webサーバを立てるか、Chromeの設定を変更する必要がある。

※Macユーザなら、ファイルのあるディレクトリに移動して、「php -S localhost:8080」でWebサーバを立てることができる。ファイルには「http://localhost:8080」というURLでアクセス可能。

transcludeプロパティ

transcludeプロパティを使うと、コンテンツを置き換えたり挿入したりする代わりにテンプレートへとコンテンツを移動できる。

使い方は以下。

表示は「こんにちはボブ」となる。

compileとlink

compileとlinkは、AngularJSのビュー生成時に実行される。

流れとしては、スクリプトの読み込み→compile→link。

compile: テンプレートを加工する
link: ビュー内のデータを加工する

compileは以下のような構造になっている。

linkは以下のようになる。

linkは$scopeにアクセスできるが、compileはできない(compileの段階では、まだ$scopeは用意されていない)。DOMの構造を変更したい場合は、postLinkの中に処理を記述する。

scopeオブジェクト

ディレクティブの設定によって、ディレクティブに渡される$scopeオブジェクトの中身が変化する。

$scopeの種類 コード
既存の$scope scope: false(デフォルト)
新しい$scope scope: true
隔離された$scope scope : {/* 属性名と関連付けの方式 */}

※新しい$scope: 包含するコントローラの$scopeを継承して生成された$scope
※隔離された$scope: 親からモデルのプロパティをまったく継承せずに生成される$scope

関連付けの方式には以下のものがある。

記号 意味
@ 属性値を文字列として渡す
= このプロパティとディレクティブにとっての親の$scopeとの間でデータバインディングを行う
& 後で呼び出すための関数を親の$scopeから渡す

controllerプロパティ

入れ子状になったディレクティブが互いにやり取りを行いたい場合には、コントローラを利用する。

requireプロパティの使用方法

文字列 使い方
directiveName どのコントローラを利用するかをキャメルケースで指定する
^directiveName DOMの木構造の中で祖先の要素に対しても探索を行う
?directiveName 対象のコントローラが見つからなかった場合に例外を発生しない

本書に載っているディレクティブの説明は以上。難しいと噂だったので気構えていたのですが、本書に載っている範囲だと、すごく難しいというほどでもないですね。設定項目は多いですが、リファレンスが手元にあれば対応できる範囲。

まあ、本書のレベルでは出てこない落とし穴が色々あるんでしょうが。。。