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 対象のコントローラが見つからなかった場合に例外を発生しない

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

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

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

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

AngularJSの呼び出し

  1. angular.jsを読み込む
  2. DOMのうちどの部分をAngularJSが管理するか指定する(ng-app)

スクリプトの読み込み

CDNの利用が推奨される(CDNを利用しているサイト間でキャッシュが効く)。

ng-appを使った境界の定義

AngularJSでページ全体を管理したい場合は、<html ng-app>、一部だけにAngularJSを使いたい場合は<div ng-app>といった指定を行う。

モデル、ビュー、コントローラ

モデル:アプリケーションの現在の状態を表すデータが含まれる
ビュー:モデルのデータを表示する
コントローラ:モデルとビューの関連性を管理する

モデルのデータをビューで表示するシンプルな方法として、「波括弧2つによる挿入(double-curry syntax interpolation)」がある。

モデルのデータは、オブジェクトとして定義することが推奨される($scopeオブジェクトの継承に起因する予期しないふるまいを防止できる)。

コントローラは、グローバルスコープではなく、モジュールに定義すべき。

テンプレートとデータバインディング

AngularJSの初期化処理の流れは以下のとおり。

  1. ユーザーがアプリケーションの初期ページ(ほとんどの場合、index.html)にアクセスする
  2. ブラウザがサーバに対してHTTP接続を行う
  3. AngularJSが読み込まれ、ng-app属性を持った要素が探索される
  4. ANgularJSはテンプレート内に記述されたディレクティブやデータバインディングを探す
  5. イベントリスナーの登録、DOMの操作、サーバからの初期データ取得が行われる
  6. アプリケーションが起動し、テンプレートはビューへと変換される
  7. 必要に応じて、サーバに接続して追加のデータを読み込む

文字列の表示

文字列の表示には、{{}}を使うやり方と、ng-bind属性を使うやり方とがある。ng-bindの使用例は以下。

これは、{{greeting}}と同じ効果をもたらす。異なる点は、ng-bindが通常のHTML要素を使うのに対し、{{}}はAngularJS独自の記法を使うこと。

{{}}を使う場合、index.htmlの初回読み込み時に、AngularJSがデータを埋め込む前に、未加工の状態のテンプレート()が短い間表示されてしまうことがある。したがって、初期ページではng-bindを使うべき。それ以外のページでは{{}}を使ってよい。

フォームへの入力

ng-model属性を使い、要素とモデルのプロパティを関連付けできる。

このコードでは、

  1. ユーザーがチェックボックスをチェックすると、SomeControllerの$scopeが持つyouCheckedItプロパティにtrueがセットされる。チェックを外すとfalseがセットされる。
  2. SomeControllerの中で$scope.youCheckedItにtrueをセットすると、UI上のチェックボックスがチェックされた状態になる。falseをセットするとチェックが外された状態になる。

input要素でng-change属性を使うと、この要素の値が変化した際にコントローラが持つ任意のメソッドが呼び出されるようにできる。

コントローラは以下(書籍のサンプルコードとは違って、モジュール内にコントローラを定義しているが、基本的な処理は同じ)。

$scope.$watch()で$scopeオブジェクトのプロパティを監視し、変更があった場合に任意のメソッドを読み出すことができる。監視対象は文字列で指定する。funding.startingEstimateプロパティそのものを監視することはできない(この値は常に初期値であるゼロに評価される)。

ng-submitディレクティブを使って、フォームの送信時に呼び出される関数を指定できる。ng-submitディレクティブが指定されていると、ブラウザによる通常のフォーム送信処理は行われない。

Unobtrusive JavaScript

Unobtrusive JavaScript(控えめなJavaScript)の前提となる知識は以下。

  1. すべてのブラウザがJavaScriptに対応しているとは限らない
  2. 制限された機能しかないブラウザを使用するユーザーがいる
  3. JavaScriptの挙動はプラットフォームに依存する
  4. イベントハンドラはグローバルな名前空間に記述される
  5. イベントハンドラでは構造とふるまいが混在して記述される

これらを踏まえて、要素にID値を割り当て、後にそれぞれの要素を参照してイベントハンドラを指定するのは、控えめなJavaScriptの考え方。

しかし、この手法では、イベントハンドラのセットアップがいたるところに散乱してしまうという問題がある。

AngularJSは、現在のWebの状況を踏まえ、以下のような立場に立つ。

  1. ほとんどのブラウザはJavaScriptをサポートしている
  2. スクリーンリーダーは進歩し、携帯電話のブラウザもJavaScriptをサポートするようになっている
  3. ブラウザ間の挙動の違いはAngularJSが吸収する
  4. AngularJSのイベントハンドラはグローバルな名前空間を使わない
  5. AngularJSでは構造とふるまいを分けて記述できる

リスト、テーブル、その他の繰り返し要素

ng-repeat属性を使って、繰り返しを定義できる。

ng-repeatディレクティブの中では、$index変数を通じて現在の通し番号(0, 1, 2…)を取得できる。また、$first, $middle, $lastによって、現在の項目がコレクション内で先頭・中間・末尾に位置するかどうかを真偽値として取得できる。

表示と非表示の切り替え

ng-showはAngular式がtrueと判定された場合に要素を表示し、falseと判定された場合に要素を非表示にする。
ng-hideはAngular式がtrueと判定された場合に要素を非表示にし、falseと判定された場合に要素を表示する。

サンプルコードだと、読み込み時に一瞬リストが表示され、それから消える、という微妙な動作になっていたので、ul要素にclass=”ng-hide”を指定して、後から復活させる部分を追加。

公式ドキュメントによると、ng-showはclass=”ng-hide”を付け外ししてるとのことなので、ng-hideを指定するだけでいけるかと思ったのだけど、実際はstyle要素でng-hideを定義してやる必要があり謎(AngularJSが使用するCSSのクラスは動的に生成されているから、アプリケーション初期化時には存在しないとか?)。

styleのscoped属性を使ってるのはグローバルに定義するのが嫌だったからだけど、HTML中にスタイル定義が入ってるのも気持ち悪さがある。悩ましい。

ついでに、jsFiddleでデモを作った。

半端なところだけど、長くなったのでひとまずここまで。

AngularJSアプリケーション開発ガイド 第1章 イントロダクション

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

これからAngularJSを業務で本格的に使うので、そろそろドットインストールレベルから脱却しようと思います。ということで上記書籍を購入しました。この本、日米のAmazonでいずれもそれほど評判が良くないです(Angularに詳しい同僚の評価も「微妙」)。しかし、日本語で読めるAngularJSの電子書籍の中で、最もページ数の少ないのは上記書籍だったので、今回はこの本にしました。

(ちなみに、同僚のオススメはRecipes with Angular.jsMastering Web Application Development with AngularJSです)

とりあえず第1章の読書メモ。

属性 用途
ng-app ページ内でのAngularJSアプリケーションの範囲を指定する
ng-controller コントローラーが担当する範囲を指定する
ng-model データバインディングを定義する
ng-repeat 繰り返しを行う

『リーダブルコード』第14章 テストと読みやすさ

リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック (Theory in practice)

テストを読みやすくて保守しやすいものにする

鍵となる考え:他のプログラマが安心してテストの追加や変更ができるように、テストコードを読みやすくする。

「大切ではない詳細はユーザから隠し、大切な詳細は目立つようにする」

テストの本質というのは、「こういう状況と入力から、こういう振る舞いと出力を期待する」のレベルまで要約できる。

テストの適切な入力値を選択する

鍵となる考え:コードを完全にテストする最も単純な入力値の組み合わせを選択しなければいけない。

鍵となる考え:テストには最もキレイで単純な値を選ぶ。

(テスト関数が)長くて変な名前にならないかと怖がることはない。他のコードから呼び出されるものではないので、長くなっても構わない。テスト関数の名前はコメントだと思えばいい。ほとんどのテスティングフレームワークでは、テストが失敗したらその関数の名前が印字されるようになっている。だから、名前は説明的なほうが役に立つのである。