AngularJS の $resource で躓いた件

2014年10月20日 15:52

O'Reilly 本の解説によれば、Web 開発者にいま最も支持されている JavaScript MVC フレームワークであるところの AngularJS。業務ではこれまで Backbone.js を主に使ってきたが、あちこちで評判のいい AngularJS にも食指が動いた次第。でもやはり、生兵法は大怪我のもとというか、ちゃんと習得せずにいきなり実装すると、落とし穴にハマるわけで……。

そんなわけで $resource である。AngularJS には $http という標準の HTTP 通信クライアントがあるが、RESTful によるサーバサイドとのデータ受け渡しを行なうための高レベル API であるところの $resource も用意されている。

さて、筆者が書いたコードはこんな感じだった。

<script src="/js/angular.min.js"></script>
<script src="/js/angular-resource.min.js"></script>
<script>
    angular.module('App', ['ngResource']).controller(
        'CompaniesController',
        [
            '$scope',
            '$resource',
            function($scope, $resource) {
                var Companies = $resource('/api/companies.json');
                $scope.companies = Companies.get();
            }
        ]
    );
</script>

世間一般の解説記事とは字下げの場所が違っていて見づらいかもしれないが、筆者にとってはこのほうが理解しやすいので我慢していただきたい。

そして HTML 側はこんな感じ。head などは省略するが、<html lang="ja" ng-app="App"> となっている前提である。

<div ng-controller="CompaniesController">
    <dl>
        <dt ng-repeat-start="company in companies">{{ company.id }}</dt>
        <dd ng-repeat-end>{{ company.name }}</dd>
    </dl>
<dl>

ツイートにも書いたが、これはうまく動かない。いや、うまく動く場合もあるのだが、筆者の環境ではダメだった。原因は /api/companies.json のレスポンス JSON の形式とサーバ通信のためのアクションとの不一致である。

このコードにおける Companies は resource クラスのインスタンスであり、サーバからデータが返されるオブジェクトである。resource クラスのインスタンスは、デフォルトのアクションによっていわゆる CRUD 操作が簡単に行なえるようになっている。そして、CRUD の read に相当するアクションが2つある。get と query だ。

結論を先に言えば、get は単体の JSON オブジェクト、query は配列化された JSON オブジェクトに対応する。「配列化された JSON オブジェクト」というのは筆者が勝手に作った表現だが、要は JSON データ自体(全体?)がオブジェクトか配列かの違い。たとえば、

{
    "id": "hogehoge",
    "name": "株式会社ほげほげ"
}

はオブジェクトであり、

[
    {
        "id": "hogehoge",
        "name": "株式会社ほげほげ"
    },
    {
        "id": "fugafuga",
        "name": "ふがふが株式会社"
    }
]

なら配列だということだ。

$resource の get アクションは本来、

var Company = $resource(
    '/api/company/:companyId',
    {
        companyId: '@id'
    }
);
$scope.company = Company.get();

みたいな使われ方を想定しており、当然、このリクエスト URI のサーバは当該 id を持つオブジェクトが1つだけの JSON を返すよう設計されていなければならない。

そもそもといえば、通信メソッドとして GET を用いるから関数も get() だろうと思い込んだ筆者の凡ミスなんですけどね。念のため、$resource のすべてのデフォルトアクションも記しておく(デフォルトとわざわざ断っているのは、カスタムアクションも定義できるから)。

{
    'get': {
        method: 'GET'
    },
    'save': {
        method: 'POST'
    },
    'query': {
        method: 'GET',
        isArray: true
    },
    'remove': {
        method: 'DELETE'
    },
    'delete': {
        method: 'DELETE'
    }
};

remove と delete がどう違うのかという新たな疑問が生まれたが、今はスルーすることにする。