Angular.js の Restangular で$resourceの置き換えが、簡単でとても便利


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

今までこんな感じで$resourceをラップするだけのサービスを使って、各Controllerで使いまわしてたけど、いくつか問題点が。


``` javascript
angular.module('models', ['ngResource'])
.factory('User', ['$resource', function ($resource) {
  return $resource(
    '/api/:Version/users/:Id',
    { Id: '@Id', Version: '@Version' },
    {
      'update': { method: 'PUT' },
      'query': { method: 'GET', isArray: false, cache: true }
    }
  );
}]);
```

- apiから返ってくる形式がちょっと違ったりすると、Controllerでデータを操作して$scopeに格納しなくてはないけない場面が多かった
- $resourceだけだとmodel同士のリレーションとか階層とか表現しづらいので、またControllerに処理を書いてしまう。
- apiごとの共通の設定とか個別の設定とか渡しにくいので、引数を増やして対応してしまって（古いバージョンのapiを呼んでるところとか）可読性やメンテナンス性が落ちていく。

などなど、色々と悩ましいことに。
Angularを書いてて思うのは、Controllerが太るけど、どうやって処理を移譲するのがいいんだろう・・。というのはよくある。

### なので偶然見つけたrestangularを試してみた


``` javascript

angular.module('app', [
  'restangular'
]);

angular.module('app').config(function(RestangularProvider) {
  // そのまんまだけどproviderにbaseのurlを渡しておける。
  RestangularProvider.setBaseUrl('/api/v3');
  // apiの返り値を共通化する処理などを書ける
  RestangularProvider.setResponseExtractor(function(response, operation) {
    return response.results;
  });
});

// 共通処理が多いので、RestangularをControllerで直接操作しないで、serviceでラップする
angular.module('services.rest', ['restangular']).
  factory('UserSvc', function(Restangular) {
    return Restangular.all('users');
  }).
  factory('IssueSvc', function(Restangular) {
    // api毎に異なる設定も可能
    return Restangular.withConfig(function(RestangularConfigurer) {
      RestangularConfigurer.setBaseUrl('/api/v2');
    }).all('issues');
  });

// GET http://localhost:8080/api/v3/usersが呼ばれて、{page:0, results: [aaa,bbb,ccc]}　結果のresultsの部分がusersに格納される
UserSvc.getList().then(
  function(users) {
    $scope.users = users;
  }
);

```

とりあえず$resourceの置き換えには困らなそう。
なにか便利な機能があったら追記していく。


### イメージしやすいと思うのでこんなふうに使ってますを貼っておく（汚いけど）
```

angular.module('services.rest', ['restangular'])

  .config(['RestangularProvider', function(RestangularProvider) {
      RestangularProvider.setBaseUrl('/api/v3');

      RestangularProvider.setRequestInterceptor(function(elem, operation) {
        if (operation === 'remove') {
           return undefined;
        }
        return elem;
      });

      RestangularProvider.setResponseExtractor(function(data, operation, what/*, url, response*/) {

        if (what === 'search' || what === 'feature' || what === 'conversations' || what === 'simple_search') {
          return data;
        }

        if (data instanceof Array) {
          return data;
        }
        if (data.result && data.result.users) {
          return data.result.users;
        }
        if (data.result && data.result.posts) {
          return data.result.posts;
        }
        if (data.result && data.result.notifications) {
          return data.result;
        }

        return data.results || data.result || data;

      });
      RestangularProvider.setDefaultHeaders({ 'X-Requested-With': 'XMLHttpRequest' });
    }
  ])

  .factory('AuthSvc', function(Restangular) {

    var svc = Restangular.all('auth');
    svc.addRestangularMethod('login', 'post', 'signin');
    svc.addRestangularMethod('signup', 'post', 'signup');
    svc.addRestangularMethod('register', 'post', 'register');
    svc.addRestangularMethod('changePassword', 'post', 'changePassword');
    svc.addRestangularMethod('inactivate', 'post', 'inactivate');
    return svc;

  })

  .factory('PasswordSvc', function(Restangular) {

    var svc = Restangular.withConfig(function(RestangularConfigurer) {
      RestangularConfigurer.setBaseUrl('/api/v3/auth');
    }).all('password');

    svc.addRestangularMethod('set', 'post', 'set');
    svc.addRestangularMethod('change', 'post', 'change');
    svc.addRestangularMethod('getKey', 'post', 'reset');
    return svc;

  })

  .factory('BankSvc', function(Restangular) {

    return Restangular.withConfig(function(RestangularConfigurer) {
      RestangularConfigurer.setBaseUrl('/api');
    }).all('accounts');

  })

  .factory('ImageUploadSvc', function(Restangular) {

    var svc = Restangular.all('images');
    svc.addRestangularMethod('getUploadUrl', 'get', 'get_upload_url');
    return svc;

  })

  .factory('MixSvc', function(Restangular) {

    var svc = Restangular.all('common');
    svc.addRestangularMethod('search', 'get', 'search');
    svc.addRestangularMethod('activities', 'get', 'activities');
    return svc;

  })

  .factory('CategorySvc', function(Restangular) {

    return Restangular.withConfig(function(RestangularConfigurer) {
      RestangularConfigurer.setDefaultHttpFields({cache: true});
    }).all('categories');

  })

  .factory('UserSvc', function(Restangular) {

    var svc = Restangular.withConfig(function(RestangularConfigurer) {
      RestangularConfigurer.setOnElemRestangularized(function(elem) {
        elem.addRestangularMethod('getTopicList', 'getList', 'topics');
        elem.addRestangularMethod('postTopic', 'post', 'topics');
        elem.addRestangularMethod('connectTags', 'post', 'tags');
        elem.addRestangularMethod('getTags', 'get', 'tags');
        elem.addRestangularMethod('getSlug', 'get', 'slug');
        return elem;
      });
    }).all('users');

    return svc;

  })

  .factory('MeSvc', function(Restangular) {
    return Restangular.withConfig(function(RestangularConfigurer) {
      RestangularConfigurer.setBaseUrl('/api/v3/profiles');
      RestangularConfigurer.setDefaultHttpFields({cache: true});
    }).all('me');
  })

  .factory('IssueSvc', function(Restangular) {
    var svc = Restangular.withConfig(function(RestangularConfigurer) {
      RestangularConfigurer.setOnElemRestangularized(function(elem) {
        elem.addRestangularMethod('publish', 'put', 'publish');
        elem.addRestangularMethod('reuse', 'post', 'reuse');
        elem.addRestangularMethod('start', 'put', 'start');
        elem.addRestangularMethod('end', 'put', 'end');
        return elem;
      });
    }).all('issues');
    svc.addRestangularMethod('search', 'get', 'search');
    svc.addRestangularMethod('getMyList', 'getList', '', {'related_me': 1});
    return svc;
  })

  .factory('ProposalSvc', function(Restangular) {

    return Restangular.withConfig(function(RestangularConfigurer) {
      RestangularConfigurer.setOnElemRestangularized(function(elem) {
        elem.addRestangularMethod('getCommentList', 'getList', 'comments');
        elem.addRestangularMethod('comment', 'post', 'comments');
        elem.addRestangularMethod('approve', 'put', 'approve');
        elem.addRestangularMethod('reject', 'put', 'reject');
        elem.addRestangularMethod('abort', 'put', 'abort');
        elem.addRestangularMethod('decide', 'put', 'decide');
        return elem;
      });
    }).all('proposals');

  })

  .factory('CardSvc', function(Restangular) {
    return Restangular.withConfig(function(RestangularConfigurer) {
      RestangularConfigurer.setBaseUrl('/api');
      RestangularConfigurer.setOnElemRestangularized(function(elem) {
        elem.addRestangularMethod('pay', 'post', 'pay');
        return elem;
      });
    }).all('payments');
  })

  .factory('TopicSvc', function(Restangular) {

    var svc = Restangular.withConfig(function(RestangularConfigurer) {
      RestangularConfigurer.setOnElemRestangularized(function(elem) {
        elem.addRestangularMethod('like', 'post', 'like');
        elem.addRestangularMethod('unlike', 'post', 'unlike');
        elem.addRestangularMethod('likedUsers', 'get', 'likes');
        return elem;
      });
    }).all('topics');
    svc.addRestangularMethod('search', 'get', 'search');
    svc.addRestangularMethod('simpleSearch', 'get', 'simple_search');
    svc.addRestangularMethod('featureList', 'get', 'feature');
    svc.addRestangularMethod('getFavList', 'getList', '', {'liked_by_me': 1, 'page_size': 50});

    return svc;

  })

  .factory('ConversationSvc', function(Restangular) {
    var svc = Restangular.all('conversations');
    svc.addRestangularMethod('getPage', 'get');
    return svc;
  })

  .factory('FacebookSvc', function(Restangular) {
    var svc = Restangular.all('facebook');
    svc.addRestangularMethod('send', 'post', 'post');
    return svc;
  })

  .factory('TagsMapSvc', function(Restangular) {
    return Restangular.withConfig(function(RestangularConfigurer) {
        RestangularConfigurer.setBaseUrl('/api/v3/tags');
        RestangularConfigurer.setDefaultHttpFields({cache: true});
    }).all('map');
  })

  .factory('ContactSvc', function(Restangular) {
    return Restangular.all('contacts');
  })

  .factory('NewsSvc', function(Restangular) {
    return Restangular.withConfig(function(RestangularConfigurer) {
        RestangularConfigurer.setBaseUrl('/api');
        RestangularConfigurer.setDefaultHttpFields({cache: true});
    }).all('news_feed');
  })

  .factory('BlogSvc', function(Restangular) {
    return Restangular.withConfig(function(RestangularConfigurer) {
        RestangularConfigurer.setBaseUrl('/api');
        RestangularConfigurer.setDefaultHttpFields({cache: true});
    }).all('posts');
  })

  .factory('SelectionSvc', function(Restangular) {
    return Restangular.withConfig(function(RestangularConfigurer) {
        RestangularConfigurer.setDefaultHttpFields({cache: true});
    }).all('selections');
  })

  .factory('IssueSelectionSvc', function(Restangular) {
    return Restangular.withConfig(function(RestangularConfigurer) {
        RestangularConfigurer.setDefaultHttpFields({cache: true});
    }).all('issue_selections');
  })

  .factory('FaqSvc', function(Restangular) {
    return Restangular.withConfig(function(RestangularConfigurer) {
        RestangularConfigurer.setBaseUrl('/templates');
        RestangularConfigurer.setDefaultHttpFields({cache: true});
    }).all('faq_ja');
  })

  .factory('MessageSvc', function(Restangular) {
    return Restangular.withConfig(function(RestangularConfigurer) {
      RestangularConfigurer.setDefaultHttpFields({cache: true});
    }).all('notifications');
  });


```


### ついでに
当社ではAngular.jsでサービスを開発したいエンジニアを募集しています！とか、書いちゃいけないのかな。