AngulaJSのTutorialのstep-11からカスタムService周りについてレポートします。
(Tutorialも応用的な仕組みであるAnimationを除くと最後のstepになります)
step-11
このstepで取り扱うServiceはAngularにとって重要なだけでなく、クライアント側の内部設計を行う上でも重要な仕組みです。なぜなら、Controllerをムダに太らせないために、Viewを複雑にさせないために、バランスよくプロダクトの構成を設計する上で必ず利用しなければならない仕組みになるためです。
今回のサンプルコードでは、このService周りの改良を加える事と一部リファクタリングが行われています。
そのリファクタリング箇所とは、$httpサービスを利用して書かれていた部分を$resourceで置き換えてServiceで内包した作りにしている部分です。それにより、これまでのstepで構築したサンプルコードのPhone情報を取り扱う部分が、よりシンプルになっています。シンプルになっているのは、$resourceのRESTfulクライアントを表現する仕組みを利用しているためです。
このstepは、カスタムServiceを作成するやり方と、クライアント側実装の根幹になる$resouceの仕組みの実装の説明を絡めて説明が構成されています。
Template
layoutテンプレートに、カスタムServiceのJSファイルと、Angularの$resourceを利用するためのJSファイルの読み込みを足しています。
<script src="js/services.js"></script>
<script src="lib/angular/angular-resource.js"></script>
Service
カスタムServiceとして下記を追加しています。
var phonecatServices = angular.module('phonecatServices', ['ngResource']);
phonecatServices.factory('Phone', ['$resource',
function($resource){
return $resource('phones/:phoneId.json', {}, {
query: {method:'GET', params:{phoneId:'phones'}, isArray:true}
});
}]);
このServiceは、Phoneという名前で作成されて、phoneリソースに対する操作を行えるリソースクラスを返してくれるものです。$resourceについては後述で触れますが、これまで$httpを使って定義していたサーバへの処理をこの$resourceを使うことで包括的に実装できます。この実装で、Serviceの戻り値を格納した変数はRESTfullクライアントに早変わりするのです。
このカスタムServiceを利用するために、下記の通り新しくphonecatAppの定義でphonecatServicesが追記されています。
angular.module('phonecatApp', ['ngRoute', 'phonecatControllers','phonecatFilters', 'phonecatServices']).
Controller
phoneに対するRESTfullクライアント実装がPhoneサービスで可能になったため、Controller側で$httpを利用した実装を行う必要がなくなりました。そのため、若干コード量が減ってシンプルになっています。
下記の通りPhoneListCtlのところが書き方が変わっています。
$http.get('phones/phones.json').success(function(data) {
$scope.phones = data;
});
↓↓↓
$scope.phones = Phone.query();
Controller内でPhoneが利用できるのは、Controllerのパラメータ部分でDIを利用して宣言しているからですね。
そして、PhoneというカスタムServiceは独自のものなので頭に「$」が付いていません。
phonecatControllers.controller('PhoneListCtrl', ['$scope', 'Phone',
function($scope, Phone) {
$scope.phones = Phone.query();
}]);
そして、$httpを利用したときに比べて直接的に結果を受け取ることができることも大きな違いです。AngularのData Bindingもはたらいて、ここで取得した結果はView側にも直接反映されます。
$resource の概要とメリット
$resourceサービスを使うことで、RESTful なサーバサイドとシンプルに連携する事のできる強力な仕組みを活用できるようになります。
$resourceは下記の通り利用します。var User = $resource('/users/:userId', {'userId': '@id'}, { 'update': {'method': 'PUT'} });
こう書くことで、User変数は、Userリソースに対する「取得(query)」・「登録/更新(save)」・「削除(remove/delete)」をRESTfulでサーバーに問い合わせを行うことが出来るようになります(カッコ内はアクション名です)。
※なんとなく中途半端だと思ったのは、PUTを利用するupdateアクションが元から準備されていないところですね。
さらに、下記のように書くことで自然にリソースに対する処理を書くことができるようになります。//ユーザリストをパラメータなしで呼び出して、取得する users = User.query(function () { //取得したユーザの一人目の名前を更新 users[0].name = '新しい名前'; users[0].$save(); //サーバの更新アクションを呼び出す });
上記の無名関数は成功時に呼び出されます。第二引数に無名関数を記述するとエラー時に実行される処理を指定できます。
取得したリソースインスタンスに対するアクションは「$」をアクション名の手前に付けます。
こうすることで、何が嬉しいか? それは、これまで$httpでサーバへの問い合わせ処理を定義していた実装を行う必要がなくなることです。上記の詳しい説明は$resource を参照
Test
テストは、$resourceが$httpの拡張であるため、基本的に$httpBackendを使っているところはかわりません。
しかし、$resourceになったことでControllerで$scope.phonesに渡されるデータがリソースオブジェクトになりました。
そのため、同じtoEqualを使ってしまうと、配列とは異なる情報が入っているということで結果がFalseになってしまいます。そこで、プロパティの情報だけを確認するためにカスタムのtoEqualDataを追加してテストを行っています。
beforeEach(function(){
this.addMatchers({
toEqualData: function(expected) {
return angular.equals(this.actual, expected);
}
});
});
期待する情報(expected)が配列であることから、実際の値(this.actual)のインスタンスのプロパティだけを比較して結果を調べてくれるようです。
以上で、step-11の説明が終わりました。
Tutorialの各step-0からstep-11までのstepは実装がシンプルなだけに簡単な仕組みを説明しているだけに見えます。
しかし、各stepの内容を紐解いていくと、JavaScriptを如何にシンプルな構成で実装できるか検討された仕組みが随所に埋め込まれていました。
それらの仕組みを理解したことで、ようやく、Angularを使って何かを作るための準備ができた事になります。
ただ、まだまだ便利なしくみや応用方法などがあり、さらに、AngularUIなどを始めとする便利ツールも各種あります。
Yoemon、BowerやKarmaの利用した開発など、昔のJavaScriptでは考えられないくらい複雑な構成でクライアントサイドの実装を行うことが必要な世の中になってしまいました。
その複雑な仕組みを如何に無理なく複数人で開発を行うためには、CIの存在は必須になってくると思います。
@Wmrfreew さんの後の回で、そのCIの利用方法に焦点を当ててレポートをまとめます。