AngularJSのngResourceモジュールをRestangularに置き換えてみた


AngularJSのngResourceモジュールをRestangularに置き換えてみた

こんにちは、フロントエンドエンジニアのいなばです。
最近引っ越して台東区民になりました。憧れだったチャリ通勤ができてとても満足しています。

今回はAngularJSで開発している案件で、Ajax通信部分をngResourceからRestangularに置き換えてみたらとても便利だったのでご紹介したいと思います。

Restangularを使った簡単なサンプルも用意してみました。
http://i78s.me/ligblog-sample/restangular-sample/demo/#/member

APIのモックサーバは「Apiary」を使っています。

RestangularとngResource

まずRestangularとngResourceについて簡単にご説明します。

ngResourceとは

RESTfulなAPIとのデータの受け渡しを行うことを想定した高レベルAPIです。
AngularJSでAjax通信をする為の機能である$httpに依存しています。

https://docs.angularjs.org/api/ngResource

Restangularとは

同じくRESTfulなAPIとのデータの受け渡しをおこなうことを想定した高レベルAPIです。AngularJSでAjax通信をするための機能である$httpに依存しています。

GitHub:https://github.com/mgonto/restangular

用途は同じなのですが、Restangularを使うとリクエストやレスポンスの前処理の段階でデータの整形などの処理をはさんだり、アプリ全体のエラーハンドリングなども簡単な記述で制御できるようになります。

ngResourceとRestangularでそれぞれモデルを実装してみる

サンプルコードも合わせてご紹介します。

ngResourceでモデルを作った場合

サンプルコード

angular.module('services').factory('MemberModel', [
    '$resource',
    'ApiUrlConstant',
    function ($resource,ApiUrlConstant) {
        var resource = $resource(ApiUrlConstant.member + '/:id',{
            id: '@id'
        }, {
            'save': {
                url: ApiUrlConstant.member,
                method: 'POST'
            },
            'get': {    // 単体を取得するとき
                method: 'GET',
                cache: true
            },
            'query': {  // 一覧を取得するとき(取得するjsonの一番外側が配列のとき)
                method: 'GET'
            },
            'update': {
                method: 'PUT',
                isArray: false
            },
            'delete': {
                method: 'DELETE'
            }
        });

        return resource;
    }
]);

Restangularでモデルを作った場合

サンプルコード

angular.module('services').factory('AccountModel', [
    'Restangular',
    function (Restangular) {
        var account = Restangular.all('account');
        return account;
    }
]);

記述量が減り大分スッキリとしました。
$resourceのquery()に相当するものがRestangularではgetList()になるなど、対応する関数の名前が多少違います。

使用例はドキュメントを参照してください。

RestangularはPromiseを返してくれるのでthenでコールバック関数を追加することができます。

.controller('member.IndexCtrl', [
    '$scope',
    'MemberModel',
    function($scope,MemberModel){
        // Promiseオブジェクトが返ってくる
        $scope.accounts = MemberModel.getList().then(function(res){
            $scope.accounts = res;
        });
    }
])
.controller('member.DetailCtrl', [
    '$scope',
    '$routeParams',
    '$location',
    'MemberModel',
    function($scope,$routeParams,$location,MemberModel){
        if(!$routeParams){
            $location.path('/member');
        }
        $scope.account = MemberModel.get($routeParams.id).$object;
    }
]);

Restangularの便利な機能

Restangularの便利な機能をご紹介します。

setBaseUrl

Angularのconfigでリクエスト先のベースとなるURLを指定することができます。

angular.module('config').config([
    'RestangularProvider',
    'ApiUrlConstant',
    function(RestangularProvider,ApiUrlConstant){
        // APIのベースURL
        RestangularProvider.setBaseUrl(ApiUrlConstant.host);
    }
]);

エンドポイントごとに個別に設定もできます。
このエンドポイントだけバージョンが違うといったときなどに使えます。

angular.module('services').factory('MemberModel', [
    'Restangular',
    'ApiUrlConstant',
    function (Restangular,ApiUrlConstant) {
        // http://private-3124d-test3708.apiary-mock.com/v2/membersにリクエストが飛ぶ
        var account = Restangular.withConfig(function(RestangularConfigurer) {
            RestangularConfigurer.setBaseUrl(ApiUrlConstant.host + 'v2');
        }).all(ApiUrlConstant.member);
        return account;
    }
]);

addResponseInterceptor

Restangularを介して行った通信のレスポンスをインターセプト(傍受)してデータの整形などの処理をはさむことができます。

angular.module('config').config([
    'RestangularProvider',
    'ApiUrlConstant',
    function(RestangularProvider,ApiUrlConstant){

        // APIのベースURL
        RestangularProvider.setBaseUrl(ApiUrlConstant.host);

        RestangularProvider.addResponseInterceptor(function (data, operation, what, url, response, deferred) {

            var extractedData;
            if (operation === "getList") {  // getListを実行したときのレスポンスで返ってきたオブジェクトを整形する
                if (data.list) {
                    extractedData = data.list || [];
                    extractedData.total = data.total;
                } else {
                    extractedData = data;
                }
            } else {
                extractedData = data;
            }
            return extractedData;
        });
    }
]);

APIから返ってきているデータ構造を、

{
    "total": 3,
    "list": [
        {/* 省略 */},
        {/* 省略 */},
        {/* 省略 */}
    ]
}

このように整形しています。

[
    {/* 省略 */},
    {/* 省略 */},
    {/* 省略 */},
    "total": 3
]

addRequestInterceptor

addResponseInterceptorの反対でRestangularを介して行った通信のリクエストを、インターセプト(傍受)して処理をはさむことができます。

angular.module('config').run([
    'Restangular',
    function(Restangular){
        Restangular.addFullRequestInterceptor(function (element, operation, route, url, headers, params, httpConfig) {
            if (/*なんらかの条件*/) {
                params.hoge = 'hoge';   // パラメータに変更を加える
            }
            return {
                headers: headers,
                params: params,
                element: element,
                httpConfig: httpConfig
            };
        });

    }
]);

setErrorInterceptor

サンプルコード

setErrorInterceptorはRestangularを介して行った通信でエラーが発生した際にインターセプトして処理を挟むことができます。
デモでは、statusが404で返ってきた場合に404ページへとリダイレクトをしています。(memberのidが5のときに意図的に404を返すようにしてあります)

angular.module('config').run([
    '$location',
    'Restangular',
    function($location,Restangular){
        Restangular.setErrorInterceptor(
            function (response, deferred, responseHandler) {
                if (response.status == 404) {
                    console.log("Resource not available...");
                    $location.path('/notFound');
                } else {
                    console.log("Response received with HTTP error code: " + response.status );
                }
                console.log(response, deferred, responseHandler);

                //return false; // stop the promise chain
            }
        );
    }
]);

setErrorInterceptorのハマりどころとして、CORS(Cross-Origin Resource Sharing)の場合に500番台エラー時のstatusが0になるので注意が必要です。

最初なぜ検知されないのかわからなかったのですが、よく見たらドキュメントに記載がありました。。

https://github.com/mgonto/restangular#restangular-fails-with-status-code-0

まとめ

いかがでしたでしょうか?
Restangularの便利な機能はまだまだあるのですが、案件で実際に使って特に便利だなと思った機能を今回はピックアップしてみました。
個人的にはAngularJSでRESTfulならRestangularは必須だなという印象です。

それでは。

 

【AngularJSの関連記事】

業務で安心して使える厳選AngularJSモジュール8選+α

AngularJSで作成したシングルページアプリケーションをGoogleアナリティクスでトラッキングさせる方法

AngularJSのOne-time Bindingを使ってパフォーマンス改善をしよう

LIG主催のAngularJS勉強会 #ngCurryが開催されました

AngularJS勉強会 ng-mtg#6に登壇してきました

この記事を書いた人

いなば
いなば フロントエンドエンジニア 2014年入社
フロントエンドエンジニアのいなばです。
LIGではAngularJSを使ったWebアプリケーションの開発をしています。
ゴリゴリ動くサイトよりSPAが得意です。
好きなものはカフェインとカプサイシン。
趣味はランニングと一眼レフです。