AngularJS
[JavaScript][AngularJS x Django][Bower]
- 以下の本を試す
準備
動作確認
- html タグにng-appディレクティブを追加
- 実行すると、「Hello AngularJS!」と表示される
<html ng-app> <head> <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.12/angular.min.js"></script> </head> <body> {{'Hello AngularJS!'}} </body> </html>
基本機能
- テンプレートエンジン
- ディレクティブ
- 双方向データバインディング
<html ng-app> <head> <meta charset="UTF-8"> <title>AngularJS Test</title> <script type="text/javascript" src="/static/js/angular.min.js"></script> </head> <body> <h1>Literal</h1> {{ 'Hello' + ' AngularJS!' }} <h1>Expression</h1> {{ 2 * 10 }} <h1>Variable</h1> <div ng-init="mynumber=123"/> {{ mynumber }} <h1>Directive</h1> <h1>if</h1> <div ng-init="showMessage=true"/> <div ng-if="showMessage"> Show Message is {{ showMessage }}. </div> <h2>repeat</h2> <div ng-init="items=['orange','apple','pine','melon']"/> <ul> <li ng-repeat="item in items">{{ item }}</li> </ul> <h1>Bi-directionally data binding</h1> <input type="text" ng-model="message" /> {{ message }} </body> </html>
コントローラとスコープ
- MyControllerで、$scopeオブジェクトにプロパティやメソッドを登録
- angular.module() でモジュール "app" を作成
- app.controller() でコントローラ myController を登録
- <html ng-app="app">と記述しappモジュールを利用可能にする
- テンプレートからコントローラを利用するには、ng-controller ディレクティブを使いコントローラ名を指定
- $scope.message に指定した値が、{{message}}と記述した箇所に展開される
- test_ag_basic2.html
<!DOCTYPE html> <html ng-app="app"> <head> <meta charset="UTF-8"> <title>AngularJS Test</title> <script type="text/javascript" src="/static/js/angular.min.js"></script> <link rel="stylesheet" href="/static/css/bootstrap.min.css"> <script type="text/javascript" src="/static/test/js/test_ag_basic2.js"></script> </head> <body> <h1>Controller and Scope</h1> <div ng-controller="myController"> <div>{{ message }}</div> <button ng-click="action()">push</button> </div> </body> </html>
- test_ag_basic2.js
var MyController = function ($scope) { $scope.message = "Hello, World!"; $scope.action = function() { $scope.message = "Goodbye, Everyone!"; }; }; var appModule = angular.module("app", []); appModule.controller("myController", MyController);
DI
- DIコンテナにより、部品間の依存関係を管理
- angular.module().value() で関数を共有できるように指定
- 共有サービスを利用するコントローラーを定義
- MyServiceとMyControllerは、異なる関数スコープの中に定義されているため、本来は呼び出せない
- MyControllerの引数にサービス名を記述すると、引数名からどのインスタンスを渡すべきか自動的に判断
- test_ag_basic2.html
<body> <h1>Controller and Scope</h1> <div ng-controller="myController"> <div>{{ message }}</div> <button ng-click="action()">push</button> <div>{{ addvalue }}</div> <button ng-click="doAdd(11,12)">add</button> </div>
- test_ag_basic2.js
appModule = angular.module("app", []); (function(){ var MyService = function(a, b) { return a + b; }; appModule.value("addService", MyService); })(); (function() { var MyController = function ($scope, addService) { $scope.message = "Hello, World!"; $scope.action = function() { $scope.message = "Goodbye, Everyone!"; }; $scope.addvalue = 0; $scope.add = addService; $scope.doAdd = function(x, y){ $scope.addvalue = $scope.add(x, y); }; }; appModule.controller("myController", MyController); })();
ビルトインディレクティブ
- AngularJS API リファレンス
DOM操作
ディレクティブ | 概要 |
---|---|
ng-bind | モデルをビューにバインド |
ng-bind-html | モデルをHTMLにバインド。ngSanitizeモジュールが読み込まれている必要がある |
ng-bind-template | テンプレートをビューにバインド |
ng-non-bindable | バインディングしたくない場合に指定 |
ng-cloak | AngularJS の初期処理がかんりょうするまで、テンプレートの表示が点滅するように表示されるのを防ぐ |
ng-style | style属性を動的に操作する |
ng-class | CSSを動的に操作する |
ng-class-even | ng-repeat と組み合わせる。偶数行に対して振る舞いを指定するのに利用 |
ng-class-odd | ng-repeat と組み合わせる。奇数行に対して振る舞いを指定するのに利用 |
ng-show | 値の評価がtrueであれば、内包するDOMを表示 |
ng-hide | 値の評価がtrueであれば、内包するDOMを非表示 |
ng-open | 定義した要素をクリックすると、内包するDOMの表示・非表示を切り替え |
ng-pluraize | 数値によって表示を切り替える |
ng-if | 値がfalseの場合、定義したDOMをDOMツリーから削除 |
ng-switch | switch文と同様の役割 |
ng-repeat | テンプレート上でループを行う |
ng-messages/ng-message | メッセージを扱う。messages と message は親子関係 |
イベント
ディレクティブ | 概要 |
---|---|
ng-click | clickイベントをリスナーに登録 |
ng-dblclick | ダブルクリックイベントをリスナーに登録 |
ng-mousedown | マウスダウンイベントをリスナーに登録 |
ng-mouseup | マウスアップイベントをリスナーに登録 |
ng-mouseenter | マウスエンターイベントをリスナーに登録 |
ng-mouseover | マウスオーバーイベントをリスナーに登録 |
ng-moucemove | マウスムーブイベントをリスナーに登録 |
ng-mouseleave | マウスリーブイベントをリスナーに登録 |
ng-focus | フォーカスイベントをリスナーに登録 |
ng-blur | ブラーイベントをリスナーに登録 |
ng-keydown | キーダウンイベントをリスナーに登録 |
ng-keypress | キープレスイベントをリスナーに登録 |
ng-keyup | キーアップイベントをリスナーに登録 |
ng-change | チェンジイベントをリスナーに登録 |
ng-checked | チェックイベントをリスナーに登録 |
ng-copy | コピーイベントをリスナーに登録、WindowsではCtrl+Cを補足する |
ng-cut | カットイベントをリスナーに登録、WindowsではCtrl+Xを補足する |
ng-paste | ペーストイベントをリスナーに登録、WindowsでCtrl+Vを補足する |
ng-submit | action属性の有無により、デフォルト動作を実行するかどうかを判定 |
$event
- $eventと呼ばれるイベントオブジェクトをラップしたオブジェクトがあります。
モジュールとDI
- AngularJSには、関数やオブジェクトをグループ化して管理するモジュールという仕組みが用意されている
- ディレクティブ、サービスやフィルター、コントローラーなどすべてモジュール機能によって管理されている
- サードパーティ製ライブラリもモジュールで管理されている
モジュールの作成と取得
- angular.moduleを利用する
- 第2引数があると、モジュールを作成し、無いと作成済みモジュールを参照する
angular.module(name, [requires], [configFn]);
ngモジュール
- ngモジュールは、依存関係を明示しなくてもどのモジュールからでも利用可能
モジュールに部品を登録する
サービス実装の比較
メソッド | サービスのDI | プリミティブ型の登録 | Module#configでの利用 | Module#decoratorによる書き換え | インスタンスの生成方法 |
---|---|---|---|---|---|
Module#value | × | ○ | × | ○ | 登録したオブジェクトそのまま |
Module#constant | × | ○ | ○ | × | 登録したオブジェクトそのまま |
Module#factory | ○ | ○ | × | ○ | 登録した関数の戻り値として渡す |
Module#service | ○ | × | × | ○ | 登録したコンストラクタ関数がnewされる |
Module#provider | ○ | ○ | ○ | ○ | $getメソッドで返す |
判定
if (directiveやproviderのコンフィギュレーションで利用するパラメータを定義したい) { Module#constant } else if (他のサービスを利用する必要がない) { Module#value } else if (Mudule#configで設定を変更したい) { Module#privider } else if (プリミティブ型を扱いたい、newでインスタンスを生成したくない){ Module#factory } else { Module#service }
Module#value
angular.Moduule#value(name,object);
- 第2引数には、数値、文字列、配列、オブジェクト、関数などを登録できる。
Module#constant
angular.Module#constant(name,value);
- 第2引数には、Module#value と同様に、数値、文字列、配列、関数などが登録できる。
Module#constantで登録したサービスは、利用できるタイミングが他よりも早くなっているため、Module#config やプロバイダのコンストラクタにインジェクトできる。
- Module#decoratorで上書きできない
- 値を書き換えることはできる
Module#factory
angular.Module#factory(name,getFn);
- 第2引数として、サービスとして共有したいオブジェクトを返す関数を記述する。
- サービスを定義するには一般的に、Module#factory か Module#service が利用される。
- Module#value、Module#constantで登録したサービスは、DIを使用して他のサービスをインジェクトできない。
- 他のサービスを利用したサービスの定義
var app = angular.module('app'); app.constant('apiUrl','/api/products.json'); app.constant('apiKey','hogehoge'); app.factory('productsService',['$resource','apiUrl','apiKey', function($resource, apiUrl, apiKey){ return $resource(apiUrl).query({apiKey: apiKey}); }]);
Module#service
angular.Module#service(name,constructor);
- Module#factoryでは共有したいオブジェクトを返す関数を登録するが、Module#serviceでは共有したいオブジェクトのコンストラクタ関数を登録する。
- サービスを定義するには一般的に、Module#factory か Module#service が利用される。
- Module#value、Module#constantで登録したサービスは、DIを使用して他のサービスをインジェクトできない。
Module#facotry、Module#service ともに、作成されたインスタンスをシングルトンとして共有することに違いはない。
- 他のサービスを利用したサービスの定義
var app = angular.module('app'); app.constant('apiUrl','/api/products.json'); app.constant('apiKey','hogehoge'); app.factory('productsService',['$resource','apiUrl','apiKey', function($resource, apiUrl, apiKey){ this.get = function() { return $resource(apiUrl).query({apiKey: apiKey}); } }]);
Module#provider
angular.Module#provider(name, provider);
- Module#value、Module#service、Module#factoryは内部でModule#providerを使用している。
- サービスを登録するためには、$getメソッドを持ったオブジェクトを定義する必要がある。
Module#configでパラメータのセッティングが可能
angular.module('app') .provider('MyService', function(){ this.$get = function() { var aService = {}; aService.doService = function() { // do something }; return aService; } });
Module#controller
Module#filter
Module#directive
Module#animation
サービス実装例
シングルトン
var app = angular.module('app'); app.service('ShareService', function(){ this.values = {}; this.setValue = function(key, value) { this.values[key] = value; }; this.getValue = function(key) { return this.values[key]; } });
新しいインスタンスを返す
- 新しいインスタンスを生成して返すサービス
- 定義
var app = angular.module('app'); app.factory('NewInstanceFactory', function(){ function NewInstance() { } NewInstance.prototype.doSomething = function() { // do something }; function NewInstanceFactory() { return new NewInstance(); } return NewInstanceFactory; });
- 利用側
var app = angular.module('app',[]); app.controller('HogeController', ['$scope','NewInstanceFactory', function($scope,NewInstanceFactory){ var ni = NewInstanceFactory(); $scope.doSomething = function() { return ni.doSomething(); } } ]);
スコープとコントローラ
- テンプレートファイルとJavaScriptコードを結びつけるためにスコープとコントローラを使用する。
利用方法
ng-controller ディレクティブを使用する
<div ng-controller="NormalController"></div>
コントローラーを定義
- Module#controller を使用する
angular.module('app',[]) .controller('NormalController',['$scope',function($scope) { $scope.message = "Hello"; } }]);
スコープ
- テンプレートに対して公開するデータや振る舞いを定義する。
- AngularJSには、DOMとJavaScriptの間にスコープと呼ばれるオブジェクトが用意されている。
- スコープのプロパティは内容の変更が監視されており、値が変化するとDOMに反映される。
- DOMの値が変更されると、対応するスコープの値に変更が反映される。
- DOMイベントが発生した場合、対応するスコープのメソッドが呼び出される。
- DOM操作はAngularJSが隠蔽するため、開発者はスコープオブジェクトを操作することで、メンテナンス性の高いコードで、画面の描画内容を変更できる。
コントローラの役割
- コントローラはスコープをセットアップする。
- DIによりインジェクトされたサービスを利用して$scopeオブジェクトを組み立てる。
サーバーサイドのMVCパターンでのコントローラとは役割が異なるため、名称に惑わされないように注意
プレゼンテーションロジックはフィルターやディレクティブに、ビジネスロジックはサービスに分離し、コントローラはあくまでもスコープのセットアップ処理に徹するのが理想的
スコープの適用範囲
- ng-controllerディレクティブを使用してコントローラーを指定した場合、その要素より下位階層の要素でのみ、そのコントローラーでセットアップしたスコープオブジェクトを利用できる。
- コントローラーは適用するごとに新しいスコープのインスタンスが生成される。
- 同一のコントローラーを複数個所に適用した場合、異なるインスタンスになる。
階層化
- コントローラーの階層化も可能
- 子コントローラーに渡されるスコープは親コントローラーのスコープの派生インスタンス
トップ階層にあるコントローラに渡されるスコープオブジェクトは、$rootScope から派生したものとなる
angular.module('app',[]) .controller('ParentController',[$scope, function($scope){ $scope.value = 'Parent'; }]) .controller('ChildController',[$scope, function($scope){ $scope.value = 'Child'; }]);
$rootScope
- $rootScopeは通常の$scope同様、コントローラにインジェクトして利用できる
- $rootScopeのインスタンスは、アプリケーション内で必ず1つのため、複数のコントローラー間で同一のインスタンスを参照可能
スコープとしてのコントローラー
- コントローラー関数をスコープオブジェクトとして利用する方法もある。
- ng-controller で コントローラー as 別名 とする
<div ng-controller="AsSyntaxController as ctrl"> <p>value: {{ctrl.vaue}}</p> <p>upperValue: {{ctrl.getUpperValue()}}</p> </div>
- コントローラー定義では、引数に$scopeを受け取らず、テンプレートに公開するプロパティやメソッドを自身(this)に登録する
angular.module('app',[]) .controller('AsSyntaxController', function(){ this.value = "Hello"; this.getUpperValue = function(){ return angular.uppercase(this.value); }; });
スコープ間連携
大規模アプリケーションを開発する場合、再利用しやすい単位ごとにコントローラーを作成したり、画面領域ごとにコントローラを分割するのが一般的
- コントローラーを分割すると、コントローラー間でのデータとやり取りや、相互に通信する手段が必要になる
- $scope オブジェクトには、コントローラー間でイベントを通知する仕組みが用意されている。
- $rootScope.Scope#$emit(name, ...args);
- 派生元スコープに対してイベントを送信
- $rootScopep.Scope#$broadcast(name ...args);
- 派生先のスコープに対してイベントを送信ん
- $rootScope.Scope#$on(name, listener);
- Scope#$emit、Scope#$broadcast で送信したイベントを受け取る
スコープの監視と反映
変更監視
- スコープオブジェクトにはデータの変化を監視するために、$watch、$watchGroup、$watchCollection が用意されている。
$rootScope.Scope#$watch
$rootScope.Scope#$watch(watchExpression, [listener],[objectEquality]);
- 第1引数には、監視対象のプロパティを文字列で指定する方法と、監視対象の値を返す関数を登録する方法がある
- 第2引数には、値が変化したときに呼び出されるリスナー関数を登録
- 第3引数にtrueを指定すると、オブジェクトを比較するときにangular.equalsを利用する。false(デフォルト) の場合、== での比較
- 戻り値はリスナー関数を解除する関数。
angula.module('app') .controller('WatchController',['$scope',function($scope){ $scope.message = 'Hello'; $scope.result = ''; $scope.$watch('message',function(newVal, oldVal, scope){ if(angular.equlas(newVal,'success') { $scope.result = 'OK'; } }); }]);
$rootScope.Scope#watchGroup
$rootScope.Scope#watchGroup(watchExpressions, listener);
- 複数のデータ値をまとめてチェックしたい場合
angular.module('app') .controller('WatchGroupController',['$scope',function($scope){ $scope.greeting = 'Hello'; $scope.message2 = 'Hello'; $scope.message3 = 'Hello'; $scope.result = ''; $scope.watchGroup([ function () { return $scope.greeting; }, function () { return $scope.message2; }, function () { return $scope.message3; } ], function(newVal, oldVal, scope) { scope.result = angular.toJson(newVal); } ); }]);
ルーティング
HTML5 History API
- HTMLL5で追加された History API history#pushState関数 および popStateイベントが利用されている。
- history.pushState()は、URLの表示をページ読み込みなしに変更出来る。
- pushState
history.pushState('','',url);
- popState
window.addEventListener('popstate', function(event){ });
AngularJSでは、ng-route モジュールで簡単に利用出来る
ng-routeの利用
モジュールの読み込み
- CDN
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular-route.min.js"></script>
Hashモードとhtml5モード
- デフォルトはHashモード。http://example.jp/#/angular ようなURLが呼び出される。
- pushStateによる操作を実行するためには、$locationProvider.html5Mode(true)を呼び出し、html5モードに変更する必要がある
IE9以下では、pushStateが実装されていないため、自動でHashモードにフォールバックされる
ルーティングの設定
when 関数の第2引数
KEY | 型 | 説明 |
---|---|---|
controller | 文字列 or 関数 | コントローラーを使用 |
controllerAs | 文字列 | コントローラーンのエイリアスを使用可能 |
template | 文字列 or 関数 | 文字列もしくは関数の戻り値でテンプレートを設定。ng-viewに展開される。templateUrlが優先 |
templateUrl | 文字列 or 関数 | 文字列もしくは関数の戻り値でテンプレートのパスを指定 |
reloadOnSearch | 真偽値 | $location#search、$location#hashで変更時に再読み込みされるrouteを指定。falseで、URLが変更されたら、$routeScopeの$routeUpdateイベントが呼ばれる |
caseInsensitiveMatch | 真偽値 | 大文字小文字が区別されずに評価。デフォルトfalse |
app.js
var app = angular.module('app', ['ngRoute']) .config(['$routeProvider','$locationProvider', function( $routeProvider, $locationProvider){ $routeProvider .when('/page/:name',{ templateUrl: 'partials/page.html', controller: 'PageController', controllerAs: 'page' }) .otherwise({ redirectTo: '/' }) ; $locationProvider.html5Mode(true); }]); app.controller('PageController',['$routeParams',function($routeParams){ this.name = 'PageController'; this.params = $routeParams;
app.html
<html ng-app="app"> <head> <base href="http://127.0.0.1/"> </head> <body> <div ng-controller="PageController as page"> <a href="page/page1">page1</a> <a href="page/page2">page2</a> <div ng-view></div> </div> : </html>
partials/page.html
<div> Page:{{ page.params.name }} </div>
プロミス
- AngularJSでは、ルーティングと呼ばれる技術を利用して、ページの部分更新やデータの取得動的な表示更新などを最小のコード量で簡単に実現できる。
- $httpなど通信処理を行う際にプロミスを知っていると便利。
- JavaScriptの特徴であるノンブロッキング処理を適切に処理するために、コールバックとプロミスがある。
プロミスとは
- プロミスとは関数の結果を返すのではなく、プロミスオブジェクトを返す。
- 同期的関数と同じように値を返すことが可能。
- ノンブロッキング処理内で成功と失敗に対するメソッドを呼び出せて、thenメソッドにより次ぎ処理を呼び出せる。
コールバックを利用すると、ノンブロッキング処理内でコールバックが続くことが多くなり、「コールバック地獄」とも呼ばれる状態に陥り、1つの関数が肥大化していくことが多々ある。これを解決するために用意されているのがプロミス。
$q
- プロミスは、Chorome 32、Firefox 29 の組込み関数として実装されているが、AngularJSでは、$qと呼ばれるサービスが用意されている
プロミスの利用
- プロミスでは、deferredと呼ばれるインスタンスと、promiseオブジェクトを一緒に利用出来る。
deferred結果
メソッド | 結果 |
---|---|
resolve | 結果が成功の場合呼び出す |
reject | 結果が失敗の場合呼び出す |
notify | resolveかrejectが呼ばれるまでに何度でも呼び出すことが可能 |
結果を待つメソッド
メソッド | 引数 | 結果¥ |
---|---|---|
then | successCallback,errorCallback,notifyCallback | 成功失敗関係なくdeferredで呼ばれるメソッドのcallbackを指定 |
catch | errorCallback | 失敗(reject)を呼び出された場合のコールバックを指定 |
finally | callback | 成功失敗関係なく最後に呼ばれるコールバックを指定 |
thenはプロミスを返すのでメソッドチェーンが可能
例
<!DOCTYPE html> <html ng-app="app"> <head> <script type="text/javascript" src="/angular/angular.min.js"></script> <script type="text/javascript"> var app = angular.module('app',[]); function checkNameLen(name){ return name.length < 5; } app.controller('PromiseCtrl',['$scope','$q','$timeout',function($scope, $q, $timeout ){ function checkAsync(name){ var deferred = $q.defer(); $timeout(function(){ deferred.notify('Async Notify'); if (checkNameLen(name)){ deferred.resolve(name); } else { deferred.reject(name); } },500); return deferred.promise; } $scope.checkName = function(){ checkAsync($scope.user_name) .then( function(msg){ $scope.msg = "SUCCESS " + msg; }, function(msg){ $scope.msg = "FAIL " + msg; }, function(msg){ $scope.msg = msg; }); } }]); </script> <title></title> </head> <body> <div ng-controller="PromiseCtrl"> <input type="text" ng-model="user_name"> <input type="button" value="check name" ng-click="checkName()"> <div>{{msg}}</div> </div> </body> </html>
jqLite
- DOMを操作するAPIとして、jQuery互換のjqLiteが用意されている
- angular.element で jqLite オブジェクトを取得できる
- https://docs.angularjs.org/api/ng/function/angular.element
jQueryの利用
- jqLiteは、jQueryと比べると機能が大きく制限されている
- AngularJS を読み込む前に jQuery を読み込むと、angular.element で取得できるオブジェクトがjQueryのものに変更される(jQuery1.x系のみ対応)
$http
$http({ method: 'POST', url: '/login', headers: { 'X-CSRFToken': getCookie('csrftoken') }, data: user, }).success(function(user){ alert(user); });
$resource
- http://js.studio-kingdom.com/angularjs/ngresource_service/$resource
- angular-resource.js の読み込み、ngResource の依存設定が必要
- angular-resource.js
<script type="text/javascript" src="js/angular-resource.js"></script>
- ngResource
var app = angular.module("app", ['ngResource']);
Tips
DjangoのCSRFトークンを送信するようにconfigで設定する
- https://docs.angularjs.org/api/ng/provider/$httpProvider
- http://django-docs-ja.readthedocs.org/en/latest/ref/contrib/csrf.html
app.config(['$httpProvider', function ($httpProvider) { $httpProvider.defaults.xsrfHeaderName = 'X-CSRFToken'; $httpProvider.defaults.xsrfCookieName = 'csrftoken'; }]);
YAGI Hiroto (piroto@a-net.email.ne.jp)
twitter http://twitter.com/pppiroto
Copyright© 矢木 浩人 All Rights Reserved.