JavaScript
AngularJS

AngularJS使い方メモ

More than 3 years have passed since last update.

AngularJSアプリケーション開発ガイド を読みながら勉強したときのメモ。

HelloWorld

AngularJS を入手する

公式サイトから angular.min.js をダウンロードする。

HTML を書く

helloWorld.html
<html ng-app>
    <head>
        <script src="angular.min.js"></script>
        <script src="helloWorld.js"></script>
    </head>
    <body>
        <h1 ng-controller="HelloWorldController">{{message}}</h1>
    </body>
</html>
  • angular.min.js を読み込む。
  • ng-app ディレクティブを付けたタグの中が、 AngularJS のテンプレートとして処理される。
  • ng-controller ディレクティブを付けたタグの中が、指定したコントローラと紐付けられる。
  • {{}} という記法で文字列を埋め込むことができる。

JavaScript を書く

helloWorld.js
function HelloWorldController($scope) {
    $scope.message = 'Hello World!!';
}
  • ng-controller ディレクティブで指定したのと同じ名前の関数を定義する。
  • 引数の $scope を介して、画面にデータ(モデル)を公開することができる。

ブラウザから見る

anglarjs_helloworld.jpg

文字列を表示する

sample.html
<html ng-app>
    <head>
        <script src="angular.min.js"></script>
        <script src="sample.js"></script>
    </head>
    <body>
        <div ng-controller="SampleController">
            <p>{{simple}}</p>
            <p ng-bind="directive"></p>
        </div>
    </body>
</html>
sample.js
function SampleController($scope) {
    $scope.simple = '簡易記法を使った方法';
    $scope.directive = 'ディレクティブを使った方法';
}

表示結果

1.JPG

2つの記法の使い分け

{{}} を使う記法は、画面初期表示時に一瞬 {{}} の状態が表示される可能性がある。
一方 ng-bind を使えば、そのようなことは発生しない。

なので、最初の画面のテンプレートは ng-bind を使い、以降のテンプレートでは {{}} を使うと良い。

フォームへの入力

sample.html
<html ng-app>
    <head>
        <script src="angular.min.js"></script>
        <script src="sample.js"></script>
    </head>
    <body>
        <form ng-controller="SampleController">
            <input type="text" ng-model="text" />

            <input type="checkbox" ng-model="checkbox" />

            <input type="radio" name="hoge" value="HOGE" ng-model="radio" />HOGE
            <input type="radio" name="hoge" value="FUGA" ng-model="radio" />FUGA

            <select ng-model="select">
                <option value="foo">Foo</option>
                <option value="bar">Bar</option>
            </select>
        </form>
    </body>
</html>
sample.js
function SampleController($scope) {
    $scope.text = 'TextBox';
    $scope.checkbox = true;
    $scope.radio = 'FUGA';
    $scope.select = 'foo';
}

実行結果

2.JPG

  • ng-model で入力項目とモデル($scope)を紐付けできる。

イベントハンドリング

<html ng-app>
    <head>
        <script src="angular.min.js"></script>
        <script src="sample.js"></script>
    </head>
    <body>
        <div ng-controller="SampleController">
            <button ng-click="click()">Button</button>
            <p>{{message}}</p>
        </div>
    </body>
</html>
sample.js
function SampleController($scope) {
    $scope.click = function() {
        $scope.message = 'click button!';
    };
}

実行結果

3.JPG

↓ボタンをクリック

4.JPG

HTMLに呼び出す関数を記述している点について

AngularJSアプリケーション開発ガイド に記載されている説明の要約?です。

この本では、「AngularJS を使うなら、 HTML に呼び出す関数を書くスタイル(宣言的なイベントハンドラ)の方がイイよ!」と主張しているっぽいです。

これまでの常識は「Unobtrusive JavaScript」

直訳すると、「控えめな JavaScript」。

要は、「文書構造は HTML に、処理は JavaScript にしっかり分離しましょう。 HTML に onclick とか書くのはダメだよ」、という考え方。

控えめじゃない JavaScript

<html>
    <head>
        <title>控えめじゃないJavaScript</title>
        <script src="sample.js"></script>
    </head>
    <body>
        <button onclick="hoge()">Button</button>
    </body>
</html>
function hoge() {
    alert('click!!');
}

控えめな JavaScript

<html>
    <head>
        <title>控えめな JavaScript</title>
        <script src="sample.js"></script>
    </head>
    <body>
        <button id="button">Button</button>
    </body>
</html>
window.onload = function() {
    document.getElementById('button').onclick = function() {
        alert('aaa!!');
    };
};

そもそも、なぜ「Unobtrusive JavaScript」が良いのか

解釈は色々あるけど、大抵の場合以下の考え方が共通している。

  1. JavaScript に対応していないブラウザだってあるんだから、 HTML に JavaScript のコード書いちゃダメでしょ!
  2. ユーザーによっては JavaScript に依存したページは良くない事があるよ!(視覚障害者はスクリーンリーダーを使うし、 JavaScript に対応していない携帯電話でアクセスする人も居る)
  3. イベント処理の呼び出し方法はブラウザごとに異なるから危ないよ!
  4. イベントハンドラはグローバルスコープで宣言しないといけないから良くないよ!
  5. 文書構造と振る舞いを混在させると、難読で保守しづらいコードができあがっちゃうよ!

本当にイベントハンドラの宣言を分離することは良いことなのか?

  • 前述の考え方を踏まえた上で JavaScript を実装すれば、ほとんどの場合は良い結果につながる。
  • しかし、実際はコードの複雑さを悪化させる要因にもなっている。
  • イベントハンドラの登録処理がコードのあちこちに散在してしまうことで、コードが複雑になっている。

2014 年現在も、この「理由」は当てはまるか?

JavaScript に対応していないブラウザだってあるんだから、 HTML に JavaScript のコード書いちゃダメでしょ!

  • 「えーマジ JavaScript 非サポート!?」
  • 「キモーイ」
  • 「JavaScript 非サポートが許されるのは江戸時代までだよねー」
  • 「キャハハハ」

今の時代、 JavaScript 非サポートはほぼ昔話になりつつあるので、もはや当てはまらないだろう、という主張。

ユーザーによっては JavaScript に依存したページは良くない事があるよ!

  • スクリーンリーダーの進歩によって、この話も過去のものになっている。
  • 携帯電話も進化したもので、デスクトップマシンと同様に JavaScript を実行できるようなっている。

つまり、これも昔話化しているという主張。

イベント処理の呼び出し方法はブラウザごとに異なるから危ないよ!

  • AngularJS を使っているなら、これは当てはまらない。
  • なぜなら、ブラウザの差は AngularJS が吸収してくれるから。
  • イベントの表記は ng-<イベント名> でブラウザに関係なく共通しているので、この問題は発生しない。

イベントハンドラはグローバルスコープで宣言しないといけないから良くないよ!

  • これも、 AngularJS を使っている限り関係無い。
  • イベントハンドラは、必ず何かしらのスコープ変数の中で宣言されるから。

文書構造と振る舞いを混在させると、難読で保守しづらいコードができあがっちゃうよ!

  • 構造と振る舞いが混在しているかどうかは、「ユニットテストを、 DOM を一切使わずに書けるか?」を確認することで確かめられる。
  • AngularJS を使っていれば、(宣言的なイベントハンドラを使っている限り) Contoller は DOM に一切アクセスすることなく画面の状態を変更することができる。
  • DOM を使わずにテストをかけると、テストがとてもシンプルになるよ!
  • 宣言的なイベントハンドラの方がシンプルで読みやすいよ!

つまり、 AngularJS を使うなら宣言的なイベントハンドラでイインダヨ!

繰り返し処理

<html ng-app>
    <head>
        <script src="angular.min.js"></script>
        <script src="sample.js"></script>
    </head>
    <body>
        <ul ng-controller="SampleController">
            <li ng-repeat="item in items">{{item.key}} : {{item.value}}</li>
        </ul>
    </body>
</html>
sample.js
function SampleController($scope) {
    $scope.items = [
        {key: 'hoge', value: 'HOGE'}
        ,{key: 'fuga', value: 'FUGA'}
        ,{key: 'piyo', value: 'PIYO'}
    ];
}

画面表示

WS000002.JPG

  • ng-repeat ディレクティブを指定したタグが、繰り返し処理される。

現在のループインデックスを取得する

<html ng-app>
    <head>
        <script src="angular.min.js"></script>
        <script src="sample.js"></script>
    </head>
    <body>
        <ul ng-controller="SampleController">
            <li ng-repeat="item in items">{{$index}}</li>
        </ul>
    </body>
</html>

画面表示

WS000000.JPG

  • $index で現在のループ回数が取得できる(0 始まり)

表紙・非表示を切り替える

<html ng-app>
    <head>
        <script src="angular.min.js"></script>
        <script src="sample.js"></script>
    </head>
    <body>
        <div ng-controller="SampleController">
            <div ng-show="true">
                Visible
            </div>
            <div ng-show="false">
                Invisible
            </div>
        </div>
    </body>
</html>

画面表紙

WS000001.JPG

  • ng-show の値が true の場合はその DOM が表示され、 false の場合は非表示になる。

CSSのクラスを指定する

<html ng-app>
    <head>
        <script src="angular.min.js"></script>
        <script src="sample.js"></script>
        <style>
            .red {
                color: red;
            }

            .blue {
                color: blue;
            }

            .solid-border {
                border: 1px solid black;
            }

            .dotted-border {
                border: 1px dotted black;
            }

            li {
                margin-top: 10px;
            }
        </style>
    </head>
    <body ng-controller="SampleController">
        <ul>
            <li ng-class="hoge">hoge</li>
            <li ng-class="['blue', 'solid-border']">fuga</li>
            <li ng-class="{'red': isRed, 'dotted-border': isDotted}">piyo</li>
        </ul>
    </body>
</html>
sample.js
function SampleController($scope) {
    $scope.hoge = 'red solid-border';
    $scope.isRed = true;
    $scope.isDotted = true;
}

画面表示

gomi.JPG

  • ng-class ディレクティブを使う。値には Angular 式を指定する。
  • Angular 式の評価結果が、
    • 「文字列」の場合、 CSS クラス名の列挙(空白区切り)として処理される。
    • 「配列」の場合、 CSS クラス名の配列として処理される。
    • 「オブジェクト」の場合、各プロパティを CSS クラス名 : 有効かどうかの boolean と判断し、 true の CSS クラスだけが適用される。
  • 自分が試した限りでは、配列やオブジェクトのキーは「文字列」にしないとうまく動作しなかった。

src 属性と href 属性にデータバインディングをする

<html ng-app>
    <head>
        <script src="angular.min.js"></script>
        <script src="sample.js"></script>
    </head>
    <body ng-controller="SampleController">
        <img ng-src="./{{imageFileName}}" />
        <a ng-href="{{url}}">link</a>
    </body>
</html>
sample.js
function SampleController($scope) {
    $scope.url = 'http://www.google.co.jp';
    $scope.imageFileName = 'hoge.png';
}

画面表示

angularjs.JPG

  • src 属性や href 属性に {{}} を使いたい場合は、それぞれ ng-src、と ng-href ディレクティブを使う。
  • なぜなら、 image タグなどは画面が表示されると同時に、 AngularJS のデータバインディングが実行される前に src 属性の URL にアクセスしてしまうため。

モデルの変更をビューに反映する

<html ng-app>
    <head>
        <script src="angular.min.js"></script>
        <script src="sample.js"></script>
    </head>
    <body ng-controller="SampleController">
        <h1>{{message}}</h1>

        <button ng-click="change()">change!!</button>
    </body>
</html>
sample.js
function SampleController($scope) {
    $scope.message = 'hoge'

    $scope.change = function() {
        $scope.message = 'change!!'
    }
}

画面表示

angularjs.JPG

↓ボタンをクリック

angularjs.JPG

  • $scope を通じてビューに表示している場合、 JS 側でその値が変われば、自動的に変更が画面に反映される。

$watch 関数で変更を監視する

<html ng-app>
    <head>
        <script src="angular.min.js"></script>
        <script src="sample.js"></script>
    </head>
    <body ng-controller="SampleController">
        hoge : <input type="number" ng-model="hoge" /><br />
        fuga : <input type="number" ng-model="fuga" /><br />
        <p>合計 : {{sum}}</p>
    </body>
</html>
sample.js
function SampleController($scope) {
    $scope.hoge = 0;
    $scope.fuga = 0;
    $scope.sum = 0;

    $scope.$watch('hoge + fuga', 'sum = hoge + fuga');
}

画面表示

angularjs.JPG

↓hoge に値を入力。

angularjs.JPG

↓fuga に値を入力。

angularjs.JPG

  • 複数の値の変化を同時に監視したい場合などは、 $watch 関数を使用する。
  • $watch 関数の引数には以下の値を渡す。
    • 第一引数(watchFn):監視する値を返す Angular 式(文字列)、または関数。
    • 第二引数(watchAction):値が変化した時に実行される Angular 式、または関数。
    • 第三引数(deepWatch):boolean 値を指定。 true を指定した場合、第一引数で指定した式(関数)が返した値のプロパティの変化も監視するようになる。

前述の例は Angular 式を使用したが、関数を使用した場合は以下のようになる。

sample.js
function SampleController($scope) {
    $scope.hoge = 0;
    $scope.fuga = 0;
    $scope.sum = 0;

    $scope.$watch(
        function() {
            return $scope.hoge + $scope.fuga;
        },
        function() {
            $scope.sum = $scope.hoge + $scope.fuga;
        }
    );
}

モジュールを定義する

<html ng-app="myModule">
    <head>
        <script src="angular.min.js"></script>
        <script src="sample.js"></script>
    </head>
    <body ng-controller="SampleController">
        <h1>{{message}}</h1>
    </body>
</html>
sample.js
(function() {
    var myModule = angular.module('myModule', []);

    myModule.controller('SampleController', function($scope) {
        $scope.message = 'module';
    });
})();

画面表示

angularjs.JPG

  • モジュールを定義すれば、その中に Controller を定義することができる(他にもサービスとか色々定義できる)。
  • ng-app の値に定義したモジュールの名前を渡す。

複数のモジュールを定義してロードする

<html ng-app="myModule">
  <head>
    <script src="angular.min.js"></script>
    <script src="sample.js"></script>
  </head>
  <body>
    <h1>{{message}}</h1>
  </body>
</html>
sample.js
angular
.module('hogeModule', [])
.service('hogeService', function() {
    this.method = function() {
        return 'hoge service';
    };
});

angular
.module('myModule', ['hogeModule'])
.run(function($rootScope, hogeService) {
    $rootScope.message = hogeService.method();
});

画面表示

angularjs.JPG

  • angular.module() の第一引数は定義するモジュールの名前。第二引数はロードするモジュールの名前を配列で指定する。

同じモジュールに関する設定を複数のファイルに跨って定義する

<html ng-app="myModule">
  <head>
    <script src="angular.min.js"></script>
    <script src="app.js"></script>
    <script src="HogeService.js"></script>
    <script src="FugaService.js"></script>
  </head>
  <body>
    <h1>{{hoge}}</h1>
    <h1>{{fuga}}</h1>
  </body>
</html>
app.js
angular
.module('myModule', [])
.run(function($rootScope, hogeService, fugaService) {
    $rootScope.hoge = hogeService.method();
    $rootScope.fuga = fugaService.method();
});
HogeService.js
angular
.module('myModule')
.service('hogeService', function() {
    this.method = function() {
        return 'hoge service';
    };
});
FugaService.js
angular
.module('myModule')
.service('fugaService', function() {
    this.method = function() {
        return 'fuga service';
    };
});

画面表示

angularjs.JPG

  • モジュールを最初に定義するときは module(<モジュール名>, []) を使用する。
  • 既に定義されているモジュールを取得するときは、 module(<モジュール名>) を使用する。
  • module(<モジュール名>, []) はモジュールの定義を上書きしてしまうので注意。
  • モジュールがまだ定義されていない状態で module(<モジュール名>) を使用するとエラーになる。

参考

依存するインスタンス(サービス)をインジェクションする

<html ng-app="myModule">
    <head>
        <script src="angular.min.js"></script>
        <script src="sample.js"></script>
    </head>
    <body ng-controller="SampleController">
        <h1>{{message}}</h1>
    </body>
</html>
sample.js
(function() {
    var myModule = angular.module('myModule', []);

    myModule.service('sampleService', SampleService);

    myModule.controller('SampleController', function($scope, sampleService) {
        $scope.message = sampleService.method();
    });

    function SampleService() {
        this.method = function() {
            return 'sample service';
        };
    }
})();

画面表示

angularjs.JPG

  • モジュールの service() メソッドで任意のクラス(サービス)を登録できる。
    • 第一引数は、サービスの名前。
    • 第二引数は、サービスのコンストラクタ関数。
  • コントローラの引数に、登録したサービス名と同じ名前の変数を定義すると、その変数にサービスのインスタンスがインジェクションされる。
  • サービスインスタンスの生成が複雑な場合は、 factory() メソッドを使う。

factory() メソッドでサービスを登録する

(function() {
    var myModule = angular.module('myModule', []);

    myModule.factory('sampleService', function() {
        return {
            method: function() {
                return 'sample service created by factory.'
            }
        };
    });

    myModule.controller('SampleController', function($scope, sampleService) {
        $scope.message = sampleService.method();
    });
})();

画面表示

angularjs.JPG

コンパイル(圧縮)対策

AngularJS は、コントローラやサービスの引数名を元に依存関係をインジェクションしている。

しかし、ソースを圧縮すると、ツールや設定によっては変数名を a のように短く変更してしまうことがある。

そうなると、この「名前によるインジェクション」ができなくなり、実行時にエラーが発生してしまう。

この対策として、 AngularJS では依存関係の名前を明示する方法が用意されている。

var module = angular.module('myModule', []);

module.controller('SampleController', ['$scope', function(s) {
    s.message = 'hoge';
}]);

コントローラやサービスの定義のときに関数を渡していたところに配列を渡すようにする。

そして、先頭から順番に引数の名前を列挙し、最後に関数本体を配置する。

こうすれば、関数本体の引数の名前は何でもよくなり、圧縮しても問題なく動作するようになる。

フィルターで表示形式を指定する

<html ng-app>
    <head>
        <script src="angular.min.js"></script>
        <script src="sample.js"></script>
    </head>
    <body ng-controller="SampleController">
        <h1>{{money | currency}}</h1>
    </body>
</html>
sample.js
function SampleController($scope) {
    $scope.money = 1000;
}

画面表示

angularjs.JPG

  • {{value | filter}} というふうに記述することで、表示形式を指定することができる(フィルターと呼ぶ)。
  • currency は、お金の表示(デフォルトはドル表記)。
  • フィルターには引数を渡せるものがあり、 {{value | filter:param1:param2}} というふうにコロン : 区切りで渡す。
  • フィルターのパラメータには Angular 式が使える。

標準のフィルター

filter

書式
{{ filter_expression | filter : expression : comparator }}
  • 配列から条件に一致した要素だけを抽出するフィルター。

expression に文字列を渡す

<html ng-app>
    <head>
        <script src="angular.min.js"></script>
        <script src="sample.js"></script>
    </head>
    <body ng-controller="SampleController">
        <pre>{{array1 | filter:"g" | json}}</pre>
        <pre>{{array2 | filter:"h" | json}}</pre>
    </body>
</html>
sample.js
function SampleController($scope) {
    $scope.array1 = ["hoge", "fuga", "piyo"];
    $scope.array2 = [
        "hoge",
        "fuga",
        {a: "hoge"},
        {a: "fuga"},
        {b: {c: "hoge"}},
        {b: {c: "fuga"}},
    ];
}

表示結果

angularjs.JPG

  • expression に文字列を指定した場合、以下のいずれかの条件に一致する要素が抽出される。
    • 指定した文字列を含む文字列要素。
    • 指定した文字列を含む文字列をプロパティの値として持つオブジェクト要素。
  • expression の文字列の先頭に ! をつけると条件を否定できる(指定した文字列を含まない要素を抽出できる)。

expression にオブジェクトを渡す

<html ng-app>
    <head>
        <script src="angular.min.js"></script>
        <script src="sample.js"></script>
    </head>
    <body ng-controller="SampleController">
        <ul>
            <li ng-repeat="physicist in physicists | filter:{firstName:'e', lastName: 'l'}">
                {{physicist.firstName}} {{physicist.lastName}}
            </li>
        </ul>
    </body>
</html>
sample.js
function SampleController($scope) {
    $scope.physicists = [
        {firstName: 'Johannes', lastName: 'Kepler'},
        {firstName: 'Galileo',  lastName: 'Galilei'},
        {firstName: 'Thomas',   lastName: 'Young'},
        {firstName: 'Michael',  lastName: 'Faraday'},
        {firstName: 'Edward',   lastName: 'Morley'},
        {firstName: 'Niels',    lastName: 'Bohr'}
    ];
}

表示結果

angularjs.JPG

  • expression にオブジェクトを渡した場合、指定した全てのプロパティにおいて、指定した文字列を含むオブジェクト要素だけが抽出される。

expression に関数を渡す

<html ng-app>
    <head>
        <script src="angular.min.js"></script>
        <script src="sample.js"></script>
    </head>
    <body ng-controller="SampleController">
        {{[1, 2, 3, 4, 5] | filter:isEvenNumber}}
    </body>
</html>
sample.js
function SampleController($scope) {
    $scope.isEvenNumber = function(number) {
        return number % 2 == 0;
    };
}

表示結果

angularjs.JPG

  • expression で関数を指定した場合、配列の各要素が関数に渡され、関数が true を返した要素だけが抽出される。

comparator で一致条件を定義する

<html ng-app>
    <head>
        <script src="angular.min.js"></script>
        <script src="sample.js"></script>
    </head>
    <body ng-controller="SampleController">
        <p>{{["a", "A", "ab", "c"] | filter:"a":true}}
        <p>{{["a", "A", "ab", "c"] | filter:"a":false}}
        <p>{{["a", "A", "ab", "c"] | filter:"a":contains}}
    </body>
</html>
sample.js
function SampleController($scope) {
    $scope.contains = function(actual, expected) {
        return actual.indexOf(expected) != -1;
    };
}

表示結果

angularjs.JPG

  • comparator で実際の値と抽出条件に指定している値との比較条件を定義できる。
  • false (デフォルト)を指定した場合は、大文字小文字関係無し、かつ文字列を含んでいれば OK になる。
  • true を指定した場合は、 angular.equals() で比較する。
  • 関数を指定した場合は、その関数が true を返した場合に一致していると判断される。

currency

書式
{{ currency_expression | currency : symbol }}
使用例
{{1000 | currency:"¥"}}
表示結果
¥1,000.00
  • お金を表示する。
  • 引数でシンボルを変更できる(デフォルトは $)。

number

書式
{{ number_expression | number : fractionSize }}
使用例
{{1000 | number:3}}
表示結果
1,000.000
  • 数値を表示する。
  • 引数で、小数点以下のサイズを指定できる。

date

書式
{{ date_expression | date : format }}
使用例
{{date | date:"yyyy/MM/dd HH:mm:ss.sss"}}
表示結果
2014/04/30 13:20:33.912
  • 日時を表示する。
  • 引数でフォーマットを指定できる(詳細は API ドキュメント を参照)。

json

書式
{{ json_expression | json }}
使用例
<pre>{{json | json}}</pre>
コントローラ
$scope.json = {
    hoge: 'HOGE',
    fuga: {
        a: true,
        b: false
    },
    piyo: [1, 2, 3]
};

表示結果

angularjs.JPG

  • JavaScript のオブジェクトを JSON 形式の文字列で表示する。

lowercase と uppercase

書式
{{ lowercase_expression | lowercase }}
{{ uppercase_expression | uppercase }}
使用例
<p>{{"HOGE" | lowercase}}</p>
<p>{{"fuga" | uppercase}}</p>
表示結果
hoge

FUGA
  • 大文字小文字を切り替える。

limitTo

書式
{{ limitTo_expression | limitTo : limit }}
使用例
<p>{{[1, 22, 333, 4444] | limitTo:3}}</p>
<p>{{"aBcDeFg" | limitTo:4}}</p>
<p>{{[1, 22, 333, 4444] | limitTo:-3}}</p>
<p>{{"aBcDeFg" | limitTo:-4}}</p>
表示結果
[1,22,333]

aBcD

[22,333,4444]

DeFg
  • 配列または文字列を、先頭から指定した数だけ取り出して表示する。
  • マイナスを指定すると、後ろから指定した数だけ取り出す。
  • 配列や文字列の長さより大きい値を引数で指定した場合は、エラーにならず全ての要素(文字)が表示される。

配列は、たぶん ng-repeat とかと組み合わせて使う。

<ul>
    <li ng-repeat="value in [1, 2, 3, 4, 5] | limitTo : 3">{{value}}</li>
</ul>

表示結果

angularjs.JPG

orderBy

書式
{{ orderBy_expression | orderBy : expression : reverse }}

expression に関数を使用する

使用例
<ul>
    <li ng-repeat="value in values | orderBy:myFunction">{{value.name}}({{value.age}})</li>
</ul>
$scope.values = [
    {name: 'taro',    age: 15},
    {name: 'takeshi', age: 12},
    {name: 'takuya',  age: 17}
];

$scope.myFunction = function(value) {
    return value.age;
};

表示結果

angularjs.JPG

  • 関数に配列の各要素が渡されるので、渡された要素の中からソートの基準にしたい値(比較演算子で比較できるもの)を取得して返す。

expression に文字列を渡す

使用例
<ul>
    <li ng-repeat="value in values | orderBy:'age'">{{value.name}}({{value.age}})</li>
</ul>
  • expression に文字列を渡すと、その文字列は配列の各要素が持つプロパティと判断され、その値でソートが行われる。
  • -age のようにすれば、ソート順を逆にできる。

expression に配列を渡す

使用例
<ul>
    <li ng-repeat="value in values | orderBy:['age', 'height']">
        {{value.name}}({{value.age}})({{value.height}} cm)
    </li>
</ul>
$scope.values = [
    {name: 'taro',    age: 15, height: 165},
    {name: 'takeshi', age: 12, height: 155},
    {name: 'taichi',  age: 15, height: 160},
    {name: 'takuya',  age: 17, height: 170}
];

表示結果

angularjs.JPG

  • expression に配列を渡すと、複数の項目でソートすることができる。
  • 配列の各要素は文字列または関数を指定する(それぞれの使い方は前述の通り)。

フィルターを自作する

<html ng-app="myModule">
    <head>
        <script src="angular.min.js"></script>
        <script src="sample.js"></script>
    </head>
    <body>
        <h1>{{"hoge" | myFilter:"<":">"}}</h1>
    </body>
</html>
sample.js
var module = angular.module('myModule', []);

module.filter('myFilter', function() {
    return function(value, param1, param2) {
        return param1 + value + param2;
    };
});

表示結果

angularjs.JPG

  • モジュールの filter() メソッドでフィルターのファクトリ関数を登録できる。
  • フィルターには、第一引数にフィルターを適用した値、第二引数以降にフィルターのパラメータが渡される。

フィルターを連結させる

使用例
<pre>{{["hoge", "fuga", "piyo"] | filter:"g" | json}}</pre>
表示結果
[
  "hoge",
  "fuga"
]
  • パイプのノリで、フィルターを連結することができる。

フィルターを JavaScript のコード上で利用する

angular
.module('myModule', [])
.run(function($filter) {
    var filter = $filter('json');
    var str = filter({name: 'Taro', age: 17});
    console.log(str);
});
ブラウザコンソール出力
{
  "name": "Taro",
  "age": 17
}
  • $filter() 関数にフィルター名を渡すと、そのフィルターの関数を取得できる。

ロケールを変更する

i18n のファイルを入手する

公式サイト から AngularJS をダウンロードするとき、 ZIP 形式でダウンロードすると、中に i18n というフォルダが入っている。

この中から、必要なロケールのファイルを見つける。

i18n のファイルを読み込む

<html ng-app>
    <head>
        <script src="angular.min.js"></script>
        <script src="angular-locale_ja-jp.js"></script>
        <script src="sample.js"></script>
    </head>
    <body ng-controller="SampleController">
        <h1>{{date | date:"yyyy/MM/dd EEE"}}</h1>
    </body>
</html>
sample.js
function SampleController($scope) {
    $scope.date = new Date();
}

表示結果

angularjs.JPG

ページの一部に動的にテンプレートを読み込む

Web サーバーを用意する

これを試す場合、ブラウザのセキュリティの制限から Web サーバ経由でページにアクセスする必要がある。

なので、 Web サーバを用意する。

Python の SimpleHTTPServer を使うか、 Apache や Tomcat や Jetty を使うか、 これ を使うか、お好きなものを。

ngRoute モジュールを追加する

AngularJS を zip でダウンロードしているなら、 angular.min.js があったフォルダと同じ所に angular-route.min.js があるので、それを読み込むようにする。

簡単な例

sample.html
<html ng-app="myModule">
    <head>
        <script src="angular.min.js"></script>
        <script src="angular-route.min.js"></script>
        <script src="sample.js"></script>
        <style>
            .border {
                border: 1px solid red;
            }
        </style>
    </head>
    <body>
        <h1>sample.html</h1>
        <div ng-view class="border"></div>
    </body>
</html>
sample.js
var module = angular.module('myModule', ['ngRoute']);

module.config(function($routeProvider) {
    $routeProvider
    .when('/', {
        controller: 'RootController',
        templateUrl: 'root.html'
    });
});

module.controller('RootController', function($scope) {
    $scope.message = 'message by RootController.';
});
root.html
<h1>Root Template</h1>
<p>{{message}}</p>

Web ブラザで sample.html にアクセスする。

表示結果

angularjs.JPG

root.html が読み込まれて、 sample.htmlng-view ディレクティブを指定した DOM に描画されている。

説明

ngRoute モジュールを有効にする。

sample.html
        <script src="angular.min.js"></script>
        <script src="angular-route.min.js"></script>
sample.js
var module = angular.module('myModule', ['ngRoute']);
  • angular-route.min.js を読み込み、モジュールを定義するときに 'ngRoute' を読み込むようにする。

動的に読み込む場所を指定する

sample.html
        <div ng-view class="border"></div>
  • ng-view ディレクティブを指定した DOM が、動的ページ切り換えのターゲットとなる。

URL ごとに表示するテンプレートとコントローラを指定する

sample.js
module.config(function($routeProvider) {
    $routeProvider
    .when('/', {
        controller: 'RootController',
        templateUrl: 'root.html'
    });
});

module.controller('RootController', function($scope) {
    $scope.message = 'message by RootController.';
});
  • モジュールの config() 関数に引数で $routeProvider を受け取るようにする。
  • $routeProviderwhen() メソッドで、 URL ごとに読み込むテンプレートとコントローラを指定する。
  • コントローラの指定は、コンストラクタ関数を渡すこともできる。

ページ切り換え

sample.html
<html ng-app="myModule">
    <head>
        <script src="angular.min.js"></script>
        <script src="angular-route.min.js"></script>
        <script src="sample.js"></script>
        <style>
            .border {
                border: 1px solid red;
                padding: 0 0 10px 10px;
            }
        </style>
    </head>
    <body>
        <h1>sample.html</h1>
        <div ng-view class="border"></div>
    </body>
</html>
sample.js
var module = angular.module('myModule', ['ngRoute']);

module.config(function($routeProvider) {
    $routeProvider
    .when('/', {
        templateUrl: 'first.html'
    })
    .when('/next', {
        templateUrl: 'second.html'
    })
    .otherwise({
        templateUrl: 'not-found.html'
    });
});
first.html
<h1>First Page</h1>
<a href="#/next">next</a>
second.html
<h1>Second Page</h1>
<a href="#/">back</a>
<a href="#/aaaa">not found</a>
not-found.html
<h1>Page Not Found</h1>

画面表示

angularjs.JPG

↓リンククリック↑

angularjs.JPG

↓not found クリック

angularjs.JPG

  • $routeProviderwhen() 関数はメソッドチェーンができる。
  • URL は Hashbang で指定する(ただし、デフォルトはバングに当たる部分 ! がない)。
  • otherwise() 関数で、その他の URL が指定された場合のルーティングを定義できる。

URLのパスの一部をパラメータとして取得する

sample.html
<html ng-app="myModule">
    <head>
        <script src="angular.min.js"></script>
        <script src="angular-route.min.js"></script>
        <script src="sample.js"></script>
    </head>
    <body>
        <div ng-view></div>
    </body>
</html>
sample.js
var module = angular.module('myModule', ['ngRoute']);

module.config(function($routeProvider) {
    $routeProvider
    .when('/:param', {
        templateUrl: 'sub.html',
        controller: function($scope, $routeParams) {
            $scope.paramValue = $routeParams.param;
        }
    });
});
sub.html
<h1>param = {{paramValue}}</h1>

ブラウザから http://localhost/sample.html#/hoge にアクセスする。

画面表示

angularjs.JPG

  • $routeProvider でルーティングを定義するときに、 :<パラメータ名> という形式でパスを定義すると、その部分をコントローラで取得することができる。
  • パスは、コントローラの引数に $routeParams を定義して、 $routeParams.<パラメータ名> で取得できる。

Hashbang を使わない

<html ng-app="myModule">
    <head>
        <script src="angular.min.js"></script>
        <script src="angular-route.min.js"></script>
        <script src="sample.js"></script>
    </head>
    <body>
        <a href="hoge">/angular/hoge</a>
        <div ng-view></div>
    </body>
</html>
sample.js
angular
.module('myModule', ['ngRoute'])
.config(function($routeProvider, $locationProvider) {

    $locationProvider.html5Mode(true);

    $routeProvider
    .when('/angular/hoge', {
        templateUrl: 'template.html'
    });
});
template.html
<h1>Template</h1>

画面表示

angularjs.JPG

↓クリック

angularjs.JPG

  • Hashbang を使わずに HTML5 の pushState を使って URL を書き換えるようにするには、 $locationProviderhtml5Mode() メソッドで true を指定する。
  • when() で指定するルート(route)は、ベース URL を基準とした絶対パスで指定する。

ただし、リンクを押した後の画面で F5 したりすると、 404 ページに飛ばされる。
mode_rewrite などで対応が必要らしい。

ページタイトルを動的に変更する

<html ng-app="myModule">
  <head>
    <script src="angular.min.js"></script>
    <script src="angular-route.min.js"></script>
    <script src="sample.js"></script>

    <title ng-bind="title + ' - Sample'"></title>
  </head>
  <body>
    <a href="#/hoge">hoge</a>

    <div ng-view></div>
  </body>
</html>
sample.js
angular
.module('myModule', ['ngRoute'])
.config(function($routeProvider) {
    $routeProvider
    .when('/', {
        title: 'Top Page',
        template: '<h1>top</h1>'
    })
    .when('/hoge', {
        title: 'Hoge Page',
        template: '<h1>hoge</h1>'
    });
})
.run(function($rootScope) {
    $rootScope.$on('$routeChangeSuccess', function(event, current, previous) {
        $rootScope.title = current.$$route.title;
    });
});

画面表示

angularjs.JPG

↓クリック

angularjs.JPG

  • title タグに ng-bind ディレクティブでタイトルを設定する。
  • $routeProvider でルーティングを定義する際、 when() メソッドのパラメータで title オプションを設定する。
  • modulerun() メソッドの中で、 $rootScope.$on("$routeChangeSuccess", callback) を使用してタイトルの変更を反映させる。

参考

サーバーリクエスト

動作確認用のサーバー側実装

SampleServlet
package sample.angular;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.text.StrBuilder;

@WebServlet("/ajax/*")
public class SampleServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    @Override
    public void service(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException {
        System.out.println("[URL] " + req.getRequestURL());
        System.out.println("[Method] " + req.getMethod());
        System.out.println("[Query String] " + req.getQueryString() + " " + this.decodeQueryString(req));
        System.out.println("[Request Body]");
        System.out.println(this.getRequestBody(req));

        String message;

        if (req.getRequestURI().contains("error")) {
            res.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            message = "Error!!";
        } else {
            res.setStatus(HttpServletResponse.SC_OK);
            message = "Success!!";
        }

        if (Boolean.valueOf(req.getParameter("array"))) {
            this.writeResponseAsArray(res, message);
        } else {
            this.writeResponseAsObject(res, message);
        }
    }

    private StrBuilder getRequestBody(HttpServletRequest req) throws IOException {
        StrBuilder sb = new StrBuilder();

        try (BufferedReader br = req.getReader()) {
            String line;
            while ((line = br.readLine()) != null) {
                sb.appendln(line);
            }
        }

        return sb;
    }

    private String decodeQueryString(HttpServletRequest req) throws UnsupportedEncodingException {
        if (req.getQueryString() != null) {
            return "(" + URLDecoder.decode(req.getQueryString(), "UTF-8") + ")";
        } else {
            return "";
        }
    }

    private void writeResponseAsObject(HttpServletResponse res, String message) throws IOException {
        this.writeResponse(res, String.format("{\\"message\\": \\"%s\\"}", message));
    }

    private void writeResponseAsArray(HttpServletResponse res, String message) throws IOException {
        this.writeResponse(res, "[" + message + "]");
    }

    private void writeResponse(HttpServletResponse res, String text) throws IOException {
        try (PrintWriter writer = res.getWriter()) {
            writer.println(text);
        }
        res.flushBuffer();
    }
}
  • /ajax/ で始まる URI にアクセスがあった場合に、そのリクエスト情報をコンソールに出力する。
  • URL に error が含まれる場合は、 Internal Server Error を返す。
  • リクエストパラメータに array=true が含まれる場合は、レスポンスを配列形式の JSON にする。それ以外はオブジェクト形式の JSON 。

基本

sample.html
<html ng-app>
    <head>
        <script src="angular.min.js"></script>
        <script src="sample.js"></script>
    </head>
    <body ng-controller="SampleController">
        <pre>{{response | json}}</pre>
    </body>
</html>
sample.js
function SampleController($scope, $http) {
    var uri = 'ajax/test';
    var param = {params: {hoge: 'HOGE', fuga: 'ふが'}};

    $http.get(uri, param)
         .success(function(data, status, headers, config) {
             log('success', data, status, headers, config);
         })
         .error(function(data, status, headers, config) {
             log('error', data, status, headers, config);
         });

    function log(type, data, status, headers, config) {
        $scope.response = {
            type: type,
            data: data,
            status: status,
            headers: headers,
            config: config
        };
    }
}

上記ファイルを Tomcat に、コンテキストルートが angular になるように配備して、 Web ブラウザで http://localhost:8080/angular/sample.html にアクセスする。

画面表示

angularjs.JPG

サーバー側コンソール出力

[URL] http://localhost:8080/angular/ajax/test
[Method] GET
[Query String] fuga=%E3%81%B5%E3%81%8C&hoge=HOGE (fuga=ふが&hoge=HOGE)
[Request Body]
  • $http をコントローラやサービスにインジェクションする。
  • $http にはサーバーにリクエストを飛ばすためのメソッド(HTTP のメソッドと対応している)が用意されている。
  • サーバーから返された文字列が JSON 形式の場合、自動的にデシリアライズされる。

REST のリソースを操作するクラスを生成する

<html ng-app="myModule">
    <head>
        <script src="angular.min.js"></script>
        <script src="angular-resource.min.js"></script>
        <script src="sample.js"></script>
    </head>
    <body ng-controller="SampleController">
    </body>
</html>
var module = angular.module('myModule', ['ngResource']);

module.controller('SampleController', function($resource, $q) {
    var Res = $resource('ajax/res');

    Res.get().$promise
    .then(function() {
        return Res.save().$promise;
    })
    .then(function() {
        return Res.delete().$promise;
    });
});
サーバー側のコンソール出力
[URL] http://localhost:8080/angular/ajax/res
[Method] GET
[Query String] null
[Request Body]

[URL] http://localhost:8080/angular/ajax/res
[Method] POST
[Query String] null
[Request Body]

[URL] http://localhost:8080/angular/ajax/res
[Method] DELETE
[Query String] null
[Request Body]
  • ngResource モジュールを読み込み有効にする。
  • $resource 関数を実行すると、指定した URL に基本的な REST リクエストを送信するメソッドを持ったオブジェクトが返される。
  • $resource 関数が返すオブジェクトを リソースクラス 。リソースクラスに定義されている REST リクエスト用のメソッドを アクション と呼ぶ。

リソースクラスにデフォルトで定義されているアクションメソッド

メソッド名 対応する HTTP メソッド サーバーから返されることを期待している JSON の型
get GET オブジェクト
save POST オブジェクト
query GET 配列
remove DELETE オブジェクト
delete DELETE オブジェクト

リソースクラスのアクションメソッドのパラメータ

GET リクエストの場合

Resource.action([parameters], [success], [error])
Res.get({a: 10}, function() {/*success*/}, function() {/*error*/});

非 GET リクエストの場合

Resource.action([parameters], postData, [success], [error]);
Res.save({a: 11}, {b: 'Bbb'}, function() {/*success*/}, function() {/*error*/});

リクエストのパスにパラメータを埋め込む

var module = angular.module('myModule', ['ngResource']);

module.controller('SampleController', function($resource, $q) {
    var Res = $resource('ajax/res/:hoge/:fuga/:piyo', {hoge: 'Hoge'});

    Res.get({fuga: 'Fuga'});
});
サーバー側の出力
[URL] http://localhost:8080/angular/ajax/res/Hoge/Fuga
[Method] GET
[Query String] null
[Request Body]
  • $resource 関数の URL に :<パラメータ名> という書式でパスを書くと、リクエスト時に値を埋め込むことができる。
  • $resource 関数の第二引数で、パラメータに埋め込む値のデフォルト値を指定できる(前述の例の :hoge)。
  • パラメータを定義したものの、リクエスト時に値を指定しなかった場合、リクエストの URL からそのパラメータの部分は除去される(前述の例の :piyo)。

リクエストデータからパラメータを埋め込む

var module = angular.module('myModule', ['ngResource']);

module.controller('SampleController', function($resource, $q) {
    var Res = $resource('ajax/res/:hoge/:fuga', {hoge: '@hoge', fuga: '@fuga'});

    var param = {hoge: 'HOGE'};
    var postData = {hoge: 'Hoge', fuga: 'Fuga'};

    Res.save(param, postData);
});
サーバー側出力
[URL] http://localhost:8080/angular/ajax/res/HOGE/Fuga
[Method] POST
[Query String] null
[Request Body]
{"hoge":"Hoge","fuga":"Fuga"}
  • パラメータのデフォルト値の先頭に @ をつけると、その値をリクエストデータから取得する。
  • ただし、埋め込みパラメータ(第一引数)で同じ名前のパラメータが指定されている場合は、そちらが優先される。
  • 主に非 GET メソッドで有用。

アクションメソッドの success 関数で、リソースクラスのインスタンスを受け取る

var module = angular.module('myModule', ['ngResource']);

module.controller('SampleController', function($resource, $q) {
    var Res = $resource('ajax/res');

    Res.get(function(res) {
        console.log('(res instanceof Res) = ' + (res instanceof Res));
        console.log('res.message = ' + res.message);
        res.hoge = 'HOGE';
        res.$save();
    });
});
ブラウザ側出力
(res instanceof Res) = true
res.message = Success!!
サーバー側出力
[URL] http://localhost:8080/angular/ajax/res
[Method] GET
[Query String] null
[Request Body]

[URL] http://localhost:8080/angular/ajax/res
[Method] POST
[Query String] null
[Request Body]
{"message":"Success!!","hoge":"HOGE"}
  • アクションメソッドの success 関数の引数で、リソースクラスのインスタンス( リソースインスタンス )が取得できる。
  • リソースインスタンスには、サーバーから返された JSON がデシリアライズされて設定されている。
  • リソースインスタンスには、 $ 始まりのアクションメソッドが用意されている($save など)。
  • リソースインスタンスのアクションを実行すると、リクエストデータにリソースインスタンスが持つプロパティが設定される。

ちなみに、アクションメソッドは success で渡しているのと同じリソースインスタンスを返す。

var module = angular.module('myModule', ['ngResource']);

module.controller('SampleController', function($resource, $q) {
    var Res = $resource('ajax/res');

    var a;

    var b = Res.get(function(res) {
        a = res;
    });

    b.$promise.then(function() {
        console.log(a === b); // true
    });
});
  • アクションメソッド( get() )の戻り値( b )は、アクションメソッドが実行された直後は空の状態で生成される。
  • サーバーからレスポンスが帰ってきたら、そのとき戻り値に値がセットされる。
  • この仕組みを利用すると、単純にサーバーから非同期で取得した値を画面に描画するだけなら、コールバック関数を書く必要がなくなる(アクションメソッドの戻り値を、そのまま $scope のプロパティにセットしてしまえば、あとは AngularJS が値の変更を監視し、値がセットされた時点で表示を更新してくれる)。

リソースインスタンスのアクションメソッドのパラメータ

instance.$action([parameters], [success], [error]);
res.$save({a: 11}, function() {/*success*/}, function() {/*error*/});

独自のアクションメソッドを定義する

var module = angular.module('myModule', ['ngResource']);

module.controller('SampleController', function($resource, $q) {
    var Res = $resource('ajax/res', {}, {myAction: {method: 'PUT'}});

    Res.myAction(function(res) {
        res.$myAction();
    });
});
サーバー側実装
[URL] http://localhost:8080/angular/ajax/res
[Method] PUT
[Query String] null
[Request Body]

[URL] http://localhost:8080/angular/ajax/res
[Method] PUT
[Query String] null
[Request Body]
{"message":"Success!!"}
  • $resource 関数の第三引数で独自のアクションメソッドを定義できる。
  • アクションメソッドの定義で指定できるオプションについては API ドキュメント を参照。

インターセプターを定義する

<html ng-app="myModule">
    <head>
        <script src="angular.min.js"></script>
        <script src="sample.js"></script>
    </head>
    <body ng-controller="SampleController">
    </body>
</html>
sample.js
var module = angular.module('myModule', []);

module.config(function($httpProvider) {
    $httpProvider.interceptors.push(function($q) {
        return {
            request: function(config) {
                console.log('request : ' + config.url);
                return config || $q.when(config);
            },

            requestError: function(rejection) {
                console.log('requestError');
                return $q.reject(rejection);
            },

            response: function(response) {
                console.log('response : ' + response.config.url);
                return response || $q.when(response);
            },

            responseError: function(rejection) {
                console.log('responseError : ' + rejection.config.url);
                return $q.reject(rejection);
            }
        };
    });
});

module.controller('SampleController', function($http, $q) {

    $q.when($http.get('ajax/success'))
    .then(success, error)
    .then(function() {
        return $http.get('ajax/error');
    })
    .then(success, error);

    function success() {
        console.log('success');
    }

    function error() {
        console.log('error');
    }
});
ブラウザコンソール出力
request : ajax/success
response : ajax/success
success
request : ajax/error
responseError : ajax/error
error
  • $httpProvider.interceptors にインターセプターのファクトリー関数を push する。
  • インターセプターには、以下の4つの関数を定義できる
    • request:リクエスト送信前に処理を挟む。
    • requestError:リクエストの送信に失敗した場合に処理を挟む。
    • response:レスポンスを受け取った直後に処理を挟む。
    • responseError:エラーのレスポンスを受け取った直後に処理を挟む。
  • ver1.1.3 までは 別の方法 でインターセプターを実装していたようだが、 1.1.4 からは非推奨になっている。

独自のディレクティブを定義する

基本

<html ng-app="myModule">
    <head>
        <script src="angular.min.js"></script>
        <script src="sample.js"></script>
    </head>
    <body ng-controller="SampleController">
        <h1 my-hoge>some string</h1>
    </body>
</html>
sample.js
angular
.module('myModule', [])
.directive('myHoge', function() {
    return {
        template: '<u>message = {{message}}</u>'
    };
})
.controller('SampleController', function($scope) {
    $scope.message = 'hoge';
});

画面表示

angularjs.JPG

  • モジュールの directive() メソッドでディレクティブを定義する。
  • directive() メソッドには、ディレクティブの名前とファクトリ関数を渡す。
  • ファクトリ関数は、ディレクティブの設定を持つオブジェクトを返すようにする。
  • 特に指定しない場合、定義したディレクティブはタグの属性として使用できる。
  • template は、ディレクティブを設定したタグの内容を、指定したテンプレート文字列で置き換える。

ディレクティブの名前について

<html ng-app="myModule">
    <head>
        <script src="angular.min.js"></script>
        <script src="sample.js"></script>
    </head>
    <body ng-controller="SampleController">
        <h1 my-hoge>some string</h1>
        <h1 my:hoge>some string</h1>
        <h1 my_hoge>some string</h1>
        <h1 data-my-hoge>some string</h1>
        <h1 data-my:hoge>some string</h1>
        <h1 data-my_hoge>some string</h1>
        <h1 x-my-hoge>some string</h1>
        <h1 x-my:hoge>some string</h1>
        <h1 x-my_hoge>some string</h1>
    </body>
</html>

画面表示

angularjs.JPG

  • ディレクティブの名前は、定義の時点では camelCase で記述する。
  • 実際に使用するときは、基本はハイフン - 繋ぎの全て小文字で指定する。
  • ただし、 HTML5 などの仕様に合わせるため、 data- などの接頭辞を付けることもできる。

ディレクティブの形態を変更する

<html ng-app="myModule">
    <head>
        <script src="angular.min.js"></script>
        <script src="sample.js"></script>
    </head>
    <body>
        <my-hoge></my-hoge>
        <h1 my-fuga></h1>
        <h1 class=my-piyo></h1>
        <h2 my-piyo></h2>
    </body>
</html>
sample.js
angular
.module('myModule', [])
.directive('myHoge', function() {
    return {
        restrict: 'E',
        template: '<h1>hoge</h1>'
    };
})
.directive('myFuga', function() {
    return {
        restrict: 'A',
        template: 'fuga'
    };
})
.directive('myPiyo', function() {
    return {
        restrict: 'CA',
        template: 'piyo'
    };
});

画面表示

angularjs.JPG

DOM 構造

angularjs.JPG

  • restrict でディレクティブの形態を指定できる。
    • A :属性として定義する。
    • E :要素として定義する。
    • C :class 属性の値として定義する。
  • AE のように複数の形態をまとめて定義できる。
  • 省略した場合は A を指定したのと同じになる。

指定したテンプレートで DOM 要素を置き換える

<html ng-app="myModule">
    <head>
        <script src="angular.min.js"></script>
        <script src="sample.js"></script>
    </head>
    <body>
        <my-hoge />
    </body>
</html>
sample.js
angular
.module('myModule', [])
.directive('myHoge', function() {
    return {
        restrict: 'E',
        replace: true,
        template: '<h1>hoge</h1>'
    };
});

画面表示

angularjs.JPG

DOM 構造

angularjs.JPG

  • replacetrue を指定すると、要素を置き換える。

別のファイルをテンプレートとして指定する

sample.html
<html ng-app="myModule">
    <head>
        <script src="angular.min.js"></script>
        <script src="sample.js"></script>
    </head>
    <body ng-controller="SampleController">
        <div my-hoge></div>
    </body>
</html>
sample.js
angular
.module('myModule', [])
.directive('myHoge', function() {
    return {
        templateUrl: 'template.html'
    };
})
.controller('SampleController', function($scope) {
    $scope.message = 'hoge';
});
template.html
<h1>template.html</h1>
<h2>message = {{message}}</h2>

画面表示

angularjs.JPG

  • templateUrl で別のファイルをテンプレートとして読み込むことができる。
  • Chrome を使用している場合は、セキュリティの都合上 Web サーバー経由でテンプレートを取得しなければならない。

テンプレートを HTML ファイルの中に埋め込んでおく

<html ng-app="myModule">
    <head>
        <script src="angular.min.js"></script>
        <script src="sample.js"></script>
    </head>
    <body ng-controller="SampleController">
        <div my-hoge></div>
    </body>
</html>

<script type="text/ng-template" id="hogeTemplate">
<h1>message = {{message}}</h1>
</script>
sample.js
angular
.module('myModule', [])
.directive('myHoge', function() {
    return {
        templateUrl: 'hogeTemplate'
    };
})
.controller('SampleController', function($scope) {
    $scope.message = 'hoge';
});

画面表示

angularjs.JPG

DOM 構造

angularjs.JPG

  • <script type="ng-template"> でテンプレートを HTML ファイルに埋め込んでおくことができる。
  • ディレクティブの定義では、 templateUrl<script> タグで指定した id 属性の値を渡す。

既存の要素をテンプレートの中に埋め込む

<html ng-app="myModule">
    <head>
        <script src="angular.min.js"></script>
        <script src="sample.js"></script>
    </head>
    <body>
        <h1 my-hoge>hoge</h1>
    </body>
</html>
sample.js
angular
.module('myModule', [])
.directive('myHoge', function() {
    return {
        transclude: true,
        template: '<del ng-transclude></del>'
    };
});

画面表示

angularjs.JPG

DOM 構造

angularjs.JPG

  • transcludetrue を指定すると、テンプレートを指定した箇所に埋め込むのではなく、逆にテンプレートに指定した箇所にある要素を埋め込むことができる。
  • 既存の内容を埋め込む場所は、 ng-transclude ディレクティブ(属性)で指定する。

ディレクティブ生成前に複雑な処理を挟む

<html ng-app="myModule">
    <head>
        <script src="angular.min.js"></script>
        <script src="sample.js"></script>
    </head>
    <body>
        <ul my-hoge id="list">
            <li>one</li>
            <li>two</li>
            <li>three</li>
        </ul>
    </body>
</html>
sample.js
angular
.module('myModule', [])
.directive('myHoge', function() {
    return {
        compile: function($element, $attr) {
            console.log('compile');
            console.log('$attr.id = ' + $attr.id);
            $element.append('<li>four</li>');
        }
    };
});

画面表示

angularjs.JPG

ブラウザコンソール出力
compile
$attr.id = list
  • compile で画面表示時に一度だけコールバックされる関数を渡せる。
  • 関数にはディレクティブを設定した要素( $element )と、要素に設定されている属性( $attr )が渡される。
  • 要素は jqLite のオブジェクトで、 jQuery とほぼ同じメソッドが利用できる。何が使えるかについては API ドキュメント を参照。
  • jqLite オブジェクトには、 AngularJS でのみ追加で使用できるメソッドが存在する(controller()scope() など)。
  • 主に、 DOM にコールバック関数を設定したり、 jQuery のプラグインを適用したりするのに利用される。

compile は一度しか呼ばれない

<html ng-app="myModule">
    <head>
        <script src="angular.min.js"></script>
        <script src="sample.js"></script>
    </head>
    <body>
        <ul>
            <li ng-repeat="i in [1, 2, 3]">
                <my-hoge />
            </li>
        </ul>
    </body>
</html>
sample.js
angular
.module('myModule', [])
.directive('myHoge', function() {
    return {
        restrict: 'E',
        compile: function($element, $attr) {
            console.log('compile');
        }
    };
});
ブラウザコンソール出力
compile

compile は1回しかコールバックされないので、 ng-repeat などによって実行時にディレクティブのインスタンスが複数出力された場合、それぞれの出力結果に対して処理を行うことができない。

そういう場合は、 link を使う。

link でディレクティブのインスタンスごとに処理を挟む

<html ng-app="myModule">
    <head>
        <script src="angular.min.js"></script>
        <script src="sample.js"></script>
    </head>
    <body>
        <ul>
            <li ng-repeat="i in [1, 2, 3]">
                <my-hoge />
            </li>
        </ul>
    </body>
</html>
sample.js
angular
.module('myModule', [])
.directive('myHoge', function() {
    return {
        restrict: 'E',
        link: function($scope, $element, $attr) {
            console.log('link');
        }
    };
});
ブラウザコンソール出力
link
link
link
  • link はディレクティブのインスタンスが生成されるごとにコールバックされる。
  • 関数に $scope が渡される。

compile と link を同時に使う

<html ng-app="myModule">
    <head>
        <script src="angular.min.js"></script>
        <script src="sample.js"></script>
    </head>
    <body>
        <ul>
            <li ng-repeat="i in [1, 2, 3]">
                <my-hoge />
            </li>
        </ul>
    </body>
</html>
sample.js
angular
.module('myModule', [])
.directive('myHoge', function() {
    return {
        restrict: 'E',
        compile: function($element, $attr) {
            console.log('compile');

            return function($scope, $element, $attr) {
                console.log('link');
            };
        }
    };
});
実行結果
compile
link
link
link
  • compilelink を同時に定義すると、 compile だけしか実行されない。
  • compilelink を同時に設定したい場合は、 compile の戻り値を link に渡していた関数にすればいい。

ディレクティブ内で使用するスコープについて

デフォルトはディレクティブを含んでいるスコープが渡される

<html ng-app="myModule">
    <head>
        <script src="angular.min.js"></script>
        <script src="sample.js"></script>
    </head>
    <body>
        <div ng-controller="SampleController">
            <my-hoge />
        </div>
    </body>
</html>
sample.js
angular
.module('myModule', [])
.controller('SampleController', function($scope) {
    $scope.message = 'sample controller';
})
.directive('myHoge', function() {
    return {
        restrict: 'E',
        link: function($scope) {
            console.log('$scope.message = ' + $scope.message);
        }
    };
});
ブラウザコンソール出力
$scope.message = sample controller
  • デフォルトでは、ディレクティブには親のスコープがそのまま渡される。
  • ディレクティブの再利用を容易にすることを考えると、親のスコープがそのまま渡されているというのはやや危険(グローバル変数と変わらない)。
  • 基本的にスコープはディレクティブ内に閉じるようにして、必要な値だけを親のスコープから渡すのが良い。
  • それを実現するには scope オプションを指定する。

親のスコープを継承した新しいスコープを使用する

<html ng-app="myModule">
    <head>
        <script src="angular.min.js"></script>
        <script src="sample.js"></script>
    </head>
    <body>
        <div ng-controller="SampleController">
            <my-hoge />
        </div>
    </body>
</html>
sample.js
var parentScope;

angular
.module('myModule', [])
.controller('SampleController', function($scope) {
    $scope.message = 'sample controller';
    parentScope = $scope;
})
.directive('myHoge', function() {
    return {
        restrict: 'E',
        scope: true,
        link: function($scope) {
            console.log('($scope === parentScope) = ' + ($scope === parentScope));
            console.log('$scope.message = ' + $scope.message);
        }
    };
});
ブラウザコンソール出力
($scope === parentScope) = false
$scope.message = sample controller
  • scopetrue を指定すると、親のスコープを継承した新しいスコープが渡される。
  • 新しいスコープなので、新規にプロパティを追加したりしても親のスコープには影響を与えない。

ディレクティブ内に隔離されたスコープを使用する

<html ng-app="myModule">
    <head>
        <script src="angular.min.js"></script>
        <script src="sample.js"></script>
    </head>
    <body>
        <div ng-controller="SampleController">
            <div my-hoge my-message="message" my-string="message, {{message}}" my-func="func()">
            </div>
        </div>
    </body>
</html>
sample.js
angular
.module('myModule', [])
.controller('SampleController', function($scope) {

    $scope.message = 'hoge fuga piyo';

    $scope.func = function() {
        return 'HOGE FUGA PIYO';
    };
})
.directive('myHoge', function() {
    return {
        scope: {
            msg: '=myMessage',
            str: '@myString',
            func: '&myFunc'
        },
        link: function($scope) {
            console.log('$scope.msg = ' + $scope.msg);
            console.log('$scope.str = ' + $scope.str);
            console.log('$scope.func() = ' + $scope.func());
        }
    };
});
ブラウザコンソール出力
$scope.msg = hoge fuga piyo
$scope.str = message, hoge fuga piyo
$scope.func() = HOGE FUGA PIYO
  • scope にオブジェクトを渡すことで、親のスコープからは完全に隔離されたスコープを作成できる。
  • scope に渡すオブジェクトの書式は、 {新しい名前 : 'ディレクティブの属性と関連付けするための式', ...}
  • ディレクティブの中では、「新しい名前」でスコープの値を参照する。
  • 「ディレクティブの属性と関連付けするための式」には以下の3つの種類がある。
  • =<属性名> :「<属性名>」で指定した名前と一致する親スコープのプロパティと関連付ける。
  • @<属性名> :「<属性名>」で指定した値を、単純な文字列として関連付ける( {{}} を使って Angular 式を書くことも可能)。
  • &<属性名> :「<属性名>」で実行している関数を関連付ける。
  • scope の宣言で使用する「<属性名>」と、実際にテンプレート上で使用する属性名のマッチング(myMessagemy-message)は、ディレクティブの名前と同じルールで行われる。
  • =<属性名> で関連付けした場合、ディレクティブ内でプロパティの値を変更すると、親スコープの対応するプロパティの値も変化する。

属性名を省略する

<html ng-app="myModule">
    <head>
        <script src="angular.min.js"></script>
        <script src="sample.js"></script>
    </head>
    <body>
        <div ng-controller="SampleController">
            <div my-hoge my-message="message" my-string="message, {{message}}" my-func="func()">
            </div>
            <button ng-click="click()">button</button>
        </div>
    </body>
</html>
sample.js
angular
.module('myModule', [])
.controller('SampleController', function($scope) {

    $scope.message = 'hoge fuga piyo';

    $scope.func = function() {
        return 'HOGE FUGA PIYO';
    };
})
.directive('myHoge', function() {
    return {
        scope: {
            myMessage: '=',
            myString: '@',
            myFunc: '&'
        },
        link: function($scope) {
            console.log('$scope.myMessage = ' + $scope.myMessage);
            console.log('$scope.myString = ' + $scope.myString);
            console.log('$scope.myFunc() = ' + $scope.myFunc());
        }
    };
});
ブラウザコンソール出力
$scope.myMessage = hoge fuga piyo
$scope.myString = message, hoge fuga piyo
$scope.myFunc() = HOGE FUGA PIYO
  • 隔離されたスコープで定義した新しい名前と属性の名前が一致する場合、属性名は省略できる。

同じタグ内のディレクティブ間で連携する

<html ng-app="myModule">
    <head>
        <script src="angular.min.js"></script>
        <script src="sample.js"></script>
    </head>
    <body>
        <div my-parent my-child>
        </div>
    </body>
</html>
angular
.module('myModule', [])
.directive('myParent', function() {
    return {
        controller: function() {
            this.method = function() {
                console.log('Parent Controller');
            };
        }
    };
})
.directive('myChild', function() {
    return {
        require: 'myParent',
        link: function($scope, $element, $attr, cntroller) {
            cntroller.method();
        }
    };
});
ブラウザコンソール出力
Parent Controller
  • ディレクティブ間で連携するときは、 controller オプションを使用する。
  • 依存される側(Parent)に controller オプションを設定し、依存する側(Child)に require オプションを設定する。
  • require オプションには、依存対象のディレクティブの名前を設定する。
  • 依存する側のディレクティブの link には、第四引数にコントローラが渡される(変数名は関係なし)。
  • require で指定したディレクティブが見つからない場合は例外がスローされる。
  • require で指定したディレクティブが見つからない場合に例外をスローさせないようにしたい場合は、 require で指定するディレクティブ名の先頭に @ を付ける( require: '@myPArent' )。

異なるタグにあるディレクティブ間で連携する

<html ng-app="myModule">
    <head>
        <script src="angular.min.js"></script>
        <script src="sample.js"></script>
    </head>
    <body>
        <my-parent>
            <my-child />
        </my-parent>
    </body>
</html>
sample.js
angular
.module('myModule', [])
.directive('myParent', function() {
    return {
        restrict: 'E',
        controller: function($scope) {
            this.method = function() {
                console.log('Parent Controller');
            };
        }
    };
})
.directive('myChild', function() {
    return {
        restrict: 'E',
        require: '^myParent',
        link: function($scope, $element, $attr, cntroller) {
            cntroller.method();
        }
    };
});
  • 依存する側(Child)の require で、ディレクティブ名の前に ^ を付けると、親タグを辿ってディレクティブを検索する(^ を付けないと、同じタグ内のディレクティブしか検索しない)。

URLを操作する

<html ng-app>
    <head>
        <script src="angular.min.js"></script>
        <script src="sample.js"></script>
    </head>
    <body ng-controller="SampleController">
    </body>
</html>
sample.js
function SampleController($location, $filter) {
    var str = 'absUrl() = ' + $location.absUrl() + '\r\n'
        + 'url() = ' + $location.url() + '\r\n'
        + 'protocol() = ' + $location.protocol() + '\r\n'
        + 'host() = ' + $location.host() + '\r\n'
        + 'port() = ' + $location.port() + '\r\n'
        + 'path() = ' + $location.path() + '\r\n'
        + 'search() = ' + $filter('json')($location.search()) + '\r\n'
        + 'hash() = ' + $location.hash() + '\r\n';
    console.log(str);
};
ブラウザコンソール出力
absUrl() = http://localhost:8080/angular/sample.html#/hoge/fuga?name=HOGE#hashValue
url() = /hoge/fuga?name=HOGE#hashValue
protocol() = http
host() = localhost
port() = 8080
path() = /hoge/fuga
search() = {
"name": "HOGE"
}
hash() = hashValue
  • $locationwindow.location をラップしたサービス。
  • いくつかのメソッドは、 setter() も兼ねている( $location.path('piyo') )。

module にある各メソッドについて

config と run

angular
.module('myModule', [])
.service('hoge', function() {
    this.method = function() {
        console.log('hoge');
    };
})
.config(function() {
    console.log('config');
})
.run(function(hoge) {
    console.log('run');
    hoge.method();
});
ブラウザコンソール出力
config
run
hoge
  • config() で登録した関数は、依存関係のインスタンスが生成される前に実行される。
  • config() に登録する関数には、サービスなどのインスタンスはインジェクションできない(実行時にエラー)。
  • プロバイダーならインジェクションできる。
  • run() で登録した関数は、依存関係のインスタンスが生成された後に実行される。
  • run() に登録する関数には、サービスなどのインスタンスをインジェクションできる。

constant と value

angular
.module('myModule', [])
.constant('hoge', 'HOGE')
.constant('fuga', {name: 'Fuga'})
.value('piyo', 'PIYO')
.config(function(hoge, fuga) {
    console.log('hoge = ' + hoge + ', fuga.name = ' + fuga.name);
    fuga.name = 'FUGA';
})
.run(function(hoge, fuga, piyo) {
    console.log('hoge = ' + hoge + ', fuga.name = ' + fuga.name+ ', piyo = ' + piyo);
});
ブラウザコンソール出力
config : hoge = HOGE, fuga.name = Fuga
run : hoge = HOGE, fuga.name = FUGA, piyo = PIYO
  • どちらも、キーバリュー形式でモジュール内でグローバルな値を定義できる。
  • constant() で定義した値は config() に登録する関数にインジェクションできる。
  • value() で定義した値は、 config() に登録する関数にインジェクションできない。
  • 値は不変ではないので、普通に変更できる。

provider

sample.js
angular
.module('myModule', [])
.provider('myHoge', function() {
    var _name;

    this.setName = function(name) {
        _name = name;
    };

    this.$get = function() {
        return {name: _name};
    };
})
.config(function(myHogeProvider) {
    myHogeProvider.setName('hoge');
})
.run(function(myHoge) {
    console.log(myHoge.name);
});
ブラウザコンソール出力
hoge
  • プロバイダー(インジェクションするインスタンスを生成するオブジェクト)を定義できる。
  • プロバイダーには $get という名前のインスタンスメソッドを持たせなければならない。
  • この $get() メソッドが返したインスタンスがインジェクションされる。

アニメーションを設定する

準備

<script src="angular-animate.min.js"></script>
angular.module('myModule', ['ngAnimate']);
  • アニメーションを有効にするには、 ngAnimate モジュールを読み込む必要がある。

簡単なサンプル

animate.html
<html>
    <head>
        <script src="angular.min.js"></script>
        <script src="angular-animate.min.js"></script>

        <script src="animate.js"></script>
        <link rel="stylesheet" href="animate.css" />
    </head>
    <body>
        <div ng-app="myModule">
            <label><input type="checkbox" ng-model="visible" /> show-hide</label>
            <h1 ng-show="visible">
                Visible
            </h1>
        </div>
    </body>
</html>
animate.js
angular.module('myModule', ['ngAnimate']);
animate.css
.ng-hide-add {
    transition: all 1.0s linear;
    display: block!important;
    opacity: 1;
}

.ng-hide-add-active {
    opacity: 0;
}

.ng-hide-remove {
    transition: all 1.0s linear;
    display: block!important;
    opacity: 0;
}

.ng-hide-remove-active {
    opacity: 1;
}

jsFiddle サンプル

仕組み

  • 特定のディレクティブで特定の変化があると、 AngularJS がそのディレクティブが設定されたタグの class 属性を変化させる。
  • class 属性の変化に合わせて、あらかじめ CSS を定義しておくことでアニメーションを実現できる。

例えば ngShow ディレクティブの場合、設定した boolean 値が切り換わるたびに class 属性が以下の要領で変化する。

ng-show が true になったとき

  1. ng-hide-add が追加される。
  2. ng-hide-add が除去されて、 ng-hide-add-active が追加される。

ng-show が false になったとき

  1. ng-hide-remove が追加される。
  2. ng-hide-remove が除去されて、 ng-hide-remove-active が追加される。

ngShow, ngHide の場合

jsFiddle サンプル

基本的な使い方は前述のサンプルの通り。

1つ注意しないと行けないのは、アニメーションを定義する ng-hide-addng-hide-remove クラスに display: block!important; を付けなければいけないという点。

.ng-hide-add {
    transition: all 1.0s linear;
    display: block!important; /*★ngShow, ngHide はこれが必要*/
}

ngRepeat の場合

jsFiddle サンプル

繰り返し処理する配列の要素が増えたとき

  1. ng-enter が追加される。
  2. ng-enter が除去され、 ng-enter-active が追加される。

繰り返し処理する配列の要素が減ったとき

  1. ng-leave が追加される。
  2. ng-leave が除去され、 ng-leave-active が追加される。

ngClass の場合

jsFiddle サンプル

  • class 属性の追加・削除に応じてアニメーションが実行される。

追加・削除される class が hoge とした場合、以下の要領で class 属性が変化する。

hoge が追加されたとき

  1. hoge-add が追加される。
  2. hoge-add が除去され、 hoge-add-active が追加される。

hoge が削除されたとき

  1. hoge-remove が追加される。
  2. hoge-remove が除去され、 hoge-remove-active が追加される。

アニメーションする要素を絞り込む

  • 単純に ng-hide-addng-enter クラスにアニメーションを設定してしまうと、全てのディレクティブがアニメーションの対象になってしまう。
  • 要素を特定してアニメーションを設定したい場合は、 .hoge.ng-hide-add のように任意のクラスを組み合わせて CSS を定義する。

jsFiddle サンプル

Cookie を操作する

<html ng-app="myModule">
    <head>
        <script src="angular.min.js"></script>
        <script src="angular-cookies.min.js"></script>
        <script src="sample.js"></script>
    </head>
    <body ng-controller="SampleController">
        <pre>{{value | json}}</pre>
    </body>
</html>
sample.js
angular
.module('myModule', ['ngCookies'])
.run(function($cookieStore) {
    $cookieStore.put('hoge', {name: 'HOGE'});
})
.controller('SampleController', function($scope, $cookieStore) {
    $scope.value = $cookieStore.get('hoge');
});

画面表示

angularjs.JPG

  • ngCookies モジュールを追加することで、 Cookie を簡単に操作できるようになる。

指定時間経過してから処理を実行する

angular
.module('myModule', [])
.run(function($timeout) {
    var promise = $timeout(function() {
        console.log('hoge');
    }, 1000);

    console.log('fuga');

    promise.then(function() {
        console.log('piyo');
    });
});
ブラウザコンソール出力
fuga
hoge
piyo
  • $timeout() は、 setTimeout() をラップした関数。
  • $timeout() の戻り値が promise オブジェクトになっている。

ログ出力

angular
.module('myModule', [])
.run(function($log) {
    $log.log('log');
    $log.debug('debug');
    $log.info('info');
    $log.warn('warn');
    $log.error('error');
});

コンソール出力

angularjs.JPG

  • $log はコンソール出力をラップした関数を持つサービス。
  • window.console が存在しないレガシーなブラウザで実行しても、エラーは発生せず無視される。
  • デフォルトは debug レベルのログが出力される。

debug レベルのログを出力させたくない場合は以下のようにする。

angular
.module('myModule', [])
.config(function($logProvider) {
    $logProvider.debugEnabled(false);
})
.run(function($log) {
    $log.log('log');
    $log.debug('debug');
    $log.info('info');
    $log.warn('warn');
    $log.error('error');
});

コンソール出力

angularjs.JPG

例外発生時に処理を挟む

<html ng-app="myModule">
    <head>
        <script src="angular.min.js"></script>
        <script src="sample.js"></script>
    </head>
    <body ng-controller="SampleController">
    </body>
</html>
sample.js
angular
.module('myModule', [])
.config(function($provide) {
    $provide.decorator('$exceptionHandler', function($delegate) {
        return function(exception, cause) {
            console.log('intercept');
            $delegate(exception, cause);
        };
    });
})
.controller('SampleController', function($scope) {
    notExists.method();
});

ブラウザコンソール出力

angularjs.JPG

参考