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

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

AngularJSの呼び出し

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

スクリプトの読み込み

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

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.27/angular.min.js"></script>

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

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

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

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

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

<p>{{someText}}</p>

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

$scope.message = "hello"; // 非推奨
$scope.message = {text: "hello"}; // 推奨

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

// 非推奨
function TextController($scope) {
    $scope.message = {text: "hello"};
}

// 推奨
var myAppModule = angular.module("myApp", []);
myAppModule.controller("TextController", function ($scope) {
    $scope.message = {text: "hello"};
});

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

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

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

文字列の表示

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

<p ng-bind="greeting"></p>

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

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

フォームへの入力

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

<form ng-controller="SomeController">
    <input type="checkbox" ng-model="youCheckedIt">
</form>

このコードでは、

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

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

<!DOCTYPE html>
<html lang="ja" ng-app="myStartUp">
<head>
    <meta charset="UTF-8">
    <title>myStartUp</title>
</head>
<body>
    <form ng-controller="StartUpController" ng-submit="requestFunding()">
        見込み:<input ng-change="computeNeeded()" ng-model="funding.startingEstimate">
        推奨額:<span ng-bind="funding.needed"></span>
        <button>出資を依頼する!</button>
    </form>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.27/angular.min.js"></script>
    <script src="controllers.js"></script>
</body>
</html>

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

(function(){
    "use strict";

    var myStartUpModule = angular.module("myStartUp", []);

    myStartUpModule.controller("StartUpController", function ($scope) {
        // 初期化
        $scope.funding = {startingEstimate: 0};

        // 必要な出資額を計算する
        var computeNeeded = function () {
            $scope.funding.needed = $scope.funding.startingEstimate * 10;
        };

        // 必要資金プロパティを監視し、変更があったら出資額を再計算
        $scope.$watch("funding.startingEstimate", computeNeeded);

        // 出資要求に対する返答
        $scope.requestFunding = function () {
            window.alert("もっと顧客を増やしてからにしてください");
        }
    });
})();

$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属性を使って、繰り返しを定義できる。

<ul ng-controller="StudentsListController">
    <li ng-repeat="student in studenst">{{$index + 1}} {{student.name}}</li>
</ul>

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

表示と非表示の切り替え

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

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body ng-app>
    <div ng-controller="DeathrayMenuController">
        <button ng-click="toggleMenu()">メニューをトグル</button>
        <style scoped>.ng-hide {display: none !important;}</style>
        <ul ng-show="menuState.show" class="ng-hide">
            <li ng-click="stun()">気絶</li>
            <li ng-click="disintegrate()">崩壊</li>
            <li ng-click="erase()">歴史から消去</li>
        </ul>
    </div>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.27/angular.min.js"></script>
    <script>
    function DeathrayMenuController($scope) {
        $scope.menuState = {show: false};

        $scope.toggleMenu = function () {
            console.log($scope.menuState.show);
            $scope.menuState.show = !$scope.menuState.show
        };
    }
    </script>
</body>
</html>

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

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

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

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

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

コメントをどうぞ

コメントを残す