AngularJS には ngResource という拡張があって、サーバに対する API 経由の CRUD 的操作を JavaScript のオブジェクトとしてラッピングできる。具体的には例えば
var Entry = $resource('/entry/:id');
var entry = Entry.get({ id : 0 }, function () {
entry.title = "yuno";
entry.$save(); // XHR (async)
});
とかできる。ちょっとかっこいいけど、既存APIで使おうとすると、些細なフォーマットの違いで案の定使えなかったりする。どうしても使ってみたいけど、サーバサイドAPIの仕様まで変えたくない場合、若干無理矢理な方法である程度なら対応させることができる。
サーバサイドの仕様
前提として以下のような仕様だとする
エントリリスト取得
GET /api/entries # Response { "ok" : true, "has_more" : true, "entries" : [ ... ] }
データの新規作成
POST /api/entries Content-Type: application/x-www-form-urlencoded title=xxx&body=yyy # Response { "ok": true "entry" : { ... } }
データの編集
PUT /api/entries?id=0 Content-Type: application/x-www-form-urlencoded title=xxx&body=yyy # Response { "ok": true "entry" : { ... } }
ngResource での対応
いくつかハマりポイントがある
- AngularJS は POST 時のデフォルト Content-Type が application/json
- ngResource は直接配列のJSONが返ってくることを前提にしている
- そして付属するデータをうまく返す方法がない
いろいろやってみると以下のようになった。
var Entry = $resource('/api/entries', { id : '@id' }, {
'query': {
method:'GET',
isArray: true,
transformResponse : function (data, headers) {
data = angular.fromJson(data);
if (!data.ok) throw "API failed";
Entry.hasMore = data.has_more;
return data.entries;
}
},
'save': {
method:'POST',
transformResponse : function (data, headers) {
data = angular.fromJson(data);
if (!data.ok) throw "API failed";
return data.entry;
},
transformRequest: function (data, headers) {
var ret = '';
for (var key in data) if (data.hasOwnProperty(key)) {
var val = data[key];
ret += '&' + encodeURIComponent(key) + '=' + encodeURIComponent(val);
}
return ret;
},
headers : {
'Content-Type' : 'application/x-www-form-urlencoded'
}
},
'update' : {
method: 'PUT',
transformResponse : function (data, headers) {
data = angular.fromJson(data);
if (!data.ok) throw "API failed";
return data.entry;
},
transformRequest: function (data, headers) {
var ret = '';
for (var key in data) if (data.hasOwnProperty(key)) {
var val = data[key];
ret += '&' + encodeURIComponent(key) + '=' + encodeURIComponent(val);
}
return ret;
},
headers : {
'Content-Type' : 'application/x-www-form-urlencoded'
}
}
});
- 自力で transformResponse, transformRequest で ngResource が要求しているフォーマットに変更してやる
- リストに付随するデータはスタティックに持たせてしまう (リクエスト直後に読み出すことを想定)
- 自力で application/x-www-form-urlencoded なリクエストを作ってやる
- 冗長に書いてるけど PUT と POST はメソッドが違うだけ
これを使う場合、
var entries = Entry.query(function () {
$scope.hasMore = Entry.hasMore;
});
...
var entry = entries[0];
entry.title = "FooBar";
entry.$update();
みたいになる。だいぶアホっぽいし、この部分のコードがカオスになるけど、一応使えるようにはなる。
もっといい方法があったら教えてください……