こんにちは、キャスレーコンサルティングのSI(システム・インテグレーション)部の池田です。
昨今、目覚しく進化し続けるJavaScriptフレームワーク。
最近では大変多くのJavaScriptのフレームワークを目にするようになりました。
今回はその中でもとりわけ人気な「AngularJS」というフレームワークについて取り上げて勉強してみました。
AngularJSとは
・Google社とコミュニティが開発するJavaScriptフレームワーク
・ライセンスはMIT License。オープンソース。商用利用可能。
・初版2009年。最新版1.5.5(2016/08/08現在)
・MVW(Model-View-Whatever)パターン
従来の画面をサーバ内で生成するものと異なり、画面をクライアント側で生成する。
いわゆるSPAと言われる手法で、最近のフロンドエンド開発のトレンドのようです。
Googleが開発元であるので信頼できるというのも人気の理由のひとつ。
環境に関して
事前準備が不要なことからCDNを利用した環境で行いました。
headタグに下記コードを記述しています。
(レンダリングブロックを気にされる方も多いかもしれませんが、今回は行いました。)
目次
今回は特徴的なものとして以下のものをサンプルコードを用いて紹介します。
1.テンプレートエンジン
2.ディレクティブによるHTML拡張
3.双方向バインディング
4.フィルター
テンプレートエンジン
そもそもテンプレートエンジンとは…?という説明は今回は割愛しますが、
要はHTMLの断片を用途や形式に合わせて良い感じに作ってくれる仕組みです。
書籍の一覧を表示するサンプルを作成してみました。
◆index.html
<pre><em id="__mceDel"><!doctype html> <html ng-app="angApp" ng-init="theme='Angular'" > <head> <meta charset="utf-8"> <title>{{theme}}関連書籍</title> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular.min.js"></script> <script type="text/javascript" src="books.js"></script> </head> <body> <h2>{{theme}}関連書籍一覧</h2> <div ng-controller="BookList"> <table border="1"> <tr> <td>No.</td> <td>タイトル</td> <td>価格</td> </tr> <tr ng-repeat="book in books"> <td>{{$index+1}}</td> <td>{{book.name}}</td> <td>{{book.price}}円</td> </tr> </table> </div> </body> </html>
◆books.js
angular.module('angApp', []) .controller('BookList',['$scope',function($scope){ $scope.books = [ { name: 'テスト書籍1', price: 1480 }, { name: 'テスト書籍2', price: 2480 }, { name: 'テスト書籍3', price: 3480 } ]; }]);
◆実行結果画面
見慣れない「ng-」というタグがいくつか存在しますが、これがAngularのディレクティブという独自の仕組みで、
DOM形成の処理をまとめておいたり、既存のタグに新しい役目を追加したりすることができます。
・ng-App
Angularを使用するルートブロックに記述する必要があります。
指定されたブロック内でモジュール(サンプルで言うところの”angApp”)がAngularの仕組みで動作します。
・ng-init
変数を定義し、初期化を行います。
・ng-controller
指定されたタグでコントローラーが動作します。コントローラーはモジュールに定義され、実際の処理や値を持ちます。
・ng-repeat
コレクションから各項目のテンプレートをインスタンス化します。所謂繰り返し処理です。
ng-repeatディレクティブはインスタンス化されたローカルスコープで特殊な変数($index:何番目に処理されているか等)の使用が可能です。
これだけ見るだけでもだいぶクライアント側で操作が可能なことがわかります。
また、書き方も複数存在するので、状況やDOMに合わせて可読性を意識した実装ができるような気がします。
ディレクティブによるHTML拡張
Directiveは直訳すると「指令」「命令」という意味がありますが、DOMに対して既存のものにとらわれない
全く新しい命令や指令を与える仕組みと解釈すると良いかもしれません。
実際にディレクティブを操作して動きを確認してみようと思います。
サンプルコードを見てみます。
◆index.html
</pre> <!doctype html> <html> <head> <meta charset="utf-8"> <title>ディレクティブ</title> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular.min.js"></script> <script type="text/javascript" src="./directive.js"></script> </head> <body> <div ng-app="angApp"> こんにちは! <casley></casley> にようこそ! </div> </body> </html> <pre>
◆directive.js
var app = angular.module("angApp", []); app.directive("casley", function () { return { restrict: "E", template: "<h2>キャスレーコンサルティング株式会社</h2>" } })
◆実行結果画面
index.htmlにはおそらく世界で唯一のcasleyタグを記述しています。
directive.jsにディレクティブの定義を記述しています。
実行結果からわかるように定義されたディレクティブのtemplateの内容がcasleyタグの部分に表示されています。
定義ではtemplateの他にrestrictの値も設定していますが、この項目は指定するタグの何を表すかを指定しています。
E:要素名
A:属性名
C:クラス名
少しサンプルを修正してみました。
◆index.html
<!doctype html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>ディレクティブ</title> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular.min.js"></script> <script type="text/javascript" src="./directive.js"></script> </head> <body> <div ng-app="angApp"> こんにちは! <casleye></casleye> <div casleya></div> <div class="casleyc"></div> にようこそ! </div> </body> </html>
◆directive.js
var app = angular.module("angApp", []); app.directive("casleye", function () { return { restrict: "E", template: "<h2>キャスレーコンサルティング株式会社(要素名)</h2>" }}) .directive("casleya", function () { return { restrict: "A", template: "<h2>キャスレーコンサルティング株式会社(属性名)</h2>" }}) .directive("casleyc", function () { return { restrict: "C", template: "<h2>キャスレーコンサルティング株式会社(クラス名)</h2>" }})
状況に応じて使い分けることで可読性の高いHTMLの実装が期待できそうですね。
折角なので簡単ですがclickイベントのバインドもやってみました。
◆index.html
<!doctype html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>ディレクティブ</title> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular.min.js"></script> <script type="text/javascript" src="./directive.js"></script> </head> <body> <div ng-app="angApp"> こんにちは! <casleye></casleye> <div casleya></div> <div class="casleyc"></div> にようこそ! </div> </body> </html>
◆directive.js
var app = angular.module("angApp", []).value( 'displist', { 'e': 'キャスレーコンサルティング株式会社(要素名)', 'a': 'キャスレーコンサルティング株式会社(属性名)', 'c': 'キャスレーコンサルティング株式会社(クラス名)' } ); app.directive("casleye", function (displist) { return { restrict: "E", template: "<h2>" + displist.e + "</h2>", link: function (scope, element) { element.on("click", function () { alert(displist.e) }) } }}) .directive("casleya", function (displist) { return { restrict: "A", template: "<h2>" + displist.a + "</h2>", link:function (scope, element) { element.on("click", function () { alert(displist.a) }) } }}) .directive("casleyc", function (displist) { return { restrict: "C", template: "<h2>" + displist.c + "</h2>", link:function (scope, element) { element.on("click", function () { alert(displist.c) }) } }})
◆実行結果画面
実行結果画面は(属性名)をクリックした場合の結果です。
ちょっと冗長だったのでモジュールにオブジェクトを定義して使いまわしてみました。
functionの引数に定義したオブジェクトを渡して利用しています。
双方向バインディング
双方向バインディングについてですが、まず、サンプルコードを見てみます。
◆index.html
<!doctype html> <html ng-app="angApp" > <head> <meta charset="utf-8"> <title>関連書籍</title> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular.min.js"></script> <script type="text/javascript" src="./books.js"></script> </head> <body ng-controller="TitleController"> <h2>{{title(categoryname)}}書籍一覧</h2> <div ng-controller="BookList"> <table border="1"> <tr> <td>No.</td> <td>タイトル</td> <td>価格</td> </tr> <tr ng-repeat="book in books"> <td>{{$index+1}}</td> <td>{{title(categoryname)+book.name}}</td> <td>{{book.price}}円</td> </tr> </table> </div> <p></p> <div > <label for="categoryname">関連書籍名:</label> <input type="text" id="categoryname" ng-model="categoryname"> </div> </body> </html>
inputタグにng-modelディレクティブが記載されていますが、これがビューとモデルとの紐付けを行っています。
◆books.js
angular.module('angApp', []) .controller('BookList',['$scope',function($scope){ $scope.books = [{ name: 'テスト書籍1', price: 1480 }, { name: 'テスト書籍2', price: 2480 }, { name: 'テスト書籍3', price: 3480 } ]; }]) .controller('TitleController',['$scope','SetTitleName',function($scope,SetTitleName) { $scope.title = function(categoryname) { return SetTitleName.title(categoryname); }; }]) .service('SetTitleName',function() { this.title = function(categoryname) { if(categoryname=== undefined || categoryname=== "") { categoryname = ""; } else{ categoryname = categoryname + "関連"; } return categoryname; }; });
新たに「TitleController」というコントローラーと「SetTitleName」というサービスを追加しました。
◆実行結果画面(ブランク時)
関連書籍名を入力できるようにしました。ここに適当なテキストを入力すると
入力したタイミングでテキストが画面のタイトルや書籍名の冒頭に追加されました。
通常であれば、テキストの変更を監視する処理や変更が検知されたタイミングで
内容を書き換えて反映するといった処理等の実装が必要ですが、コントローラとサービスを定義するだけで実現できてしまいます。
このように画面とビジネスロジックの状態をディレクティブによって紐つけ、
継続的に同期し続ける仕組みが「双方向バインディング」です。
フィルター
フィルターはカスタムして実装することもでき、いろいろな事ができる分、全てを理解するのは少し難しいです。
簡単ですが先ほどの書籍のサンプルに難易度フィルターを追記し、「3」が含まれるものをフィルターできるように追記しました。
◆index.html
<!doctype html> <html ng-app="angApp" > <head> <meta charset="utf-8"> <title>関連書籍</title> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular.min.js"></script> <script type="text/javascript" src="./books.js"></script> </head> <body ng-controller="TitleController"> <h2>{{title(categoryname)}}書籍一覧</h2> <div ng-controller="BookList"> <table border="1"> <tr> <td>No.</td> <td>タイトル</td> <td>価格</td> </tr> <tr ng-repeat="book in books"> <td>{{$index+1}}</td> <td>{{title(categoryname)+book.name}}</td> <td>{{book.price}}円</td> </tr> </table> <p></p> ■難易度フィルター■ <li ng-repeat="book in books"> {{title(categoryname)+book.name | target}} </li> </div> <p></p> <div> <label for="categoryname">関連書籍名:</label> <input type="text" id="categoryname" ng-model="categoryname"> </div> </body> </html>
◆books.js
angular.module('angApp', []) .controller('BookList',['$scope',function($scope){ $scope.books = [{ name: 'テスト書籍1', price: 1480 }, { name: 'テスト書籍2', price: 2480 }, { name: 'テスト書籍3', price: 3480 } ]; }]) .controller('TitleController',['$scope','SetTitleName',function($scope,SetTitleName) { $scope.title = function(categoryname) { return SetTitleName.title(categoryname); }; }]) .service('SetTitleName',function() { this.title = function(categoryname) { if(categoryname=== undefined || categoryname=== "") { categoryname = ""; } else{ categoryname = categoryname + "関連"; } return categoryname; }; }) .filter('target', function() { return function(input) { var ret; if(input.indexOf('3')>-1){ ret = input + "!難易度高!"; } else{ ret = input; } return ret; }; });
◆実行結果画面
まとめ
ざっと一通り目ぼしい特徴を学習してきましたが、
まだまだ語りきれないAngularの技術はもりだくさんなので、
時間を見つけて学んでいきたいと思っています。
上述してきたように、Angularは多くの機能を持つフレームワークで、
一通り開発に必要な機能を内包している(フルスタック)という点も大きな特徴のひとつです。
大人数で開発したりする場合は他のフレームワークより適してるのかもしれません。
まさに、これさえあれば、何もいらない。
JavaScriptのフレームワークはたくさんありますが、
実装コード量は多いが汎用的なフレームワークと、
実装コード量は少ないが使い所を選ぶフレームワークに分かれるとすれば、
Angularは後者になるのかなという印象を受けました。
メンテナンス画面や情報の照会画面等ではAngularは力を発揮すると思います。
しかし、ディレクティブ等の独自性が強く、
Angularを極めたから他の言語で同じような書き方が通用することは少ないのかもしれません。
そして、取り扱っておいてアレなのですが、AngularJSはもうすぐ2系が出るらしいです。
既にベータ版が出ていて、どうやら1系とはだいぶ異なる仕様が多いみたいです。
これからAngularを勉強しようという方は2系から始めた方がいいかもしれません。。。
最後まで読んで頂き、ありがとうございました。