2015-02-14

AngularJSでinput[type=date]にデータバインドしようとしてError: [ngModel:datefmt]で怒られて困っている人向けの小ネタ

タイトルの通りです。割と遭遇している人いるんじゃないかなと勝手に想像しているんですが、あまり情報ないので書いてみます。

ユースケース的には、WebAPIアクセスで日付をJSONシリアライズしたものを受け取って、input[type=date]のng−modelに直接データバインドするような想定です。

サンプルコード

(適当に書いているので動かなかったらすみません。雰囲気だけでも。)

WebAPI側からJSONデータ

{
  "id":1,
  "name": "ググレカス",
  "createdAt":"2015-02-05T00:00:00.000Z"
}

AngularJSのDataService的コード(今回はfactory)

angular.module('app').factory('ggrks', function($resource) {
  return $resource('/api/ggrks');
});

AngularJSのController

angular.module('app').controller('ggrksController', function($scope, ggrks) {
  // 初回データアクセス
  ggrks.query(function(data) {
    $scope.ggrks = data;
  });
});

AngularJSのテンプレート

<input type="date" ng-model="ggrks.createdAt"/>

このまま実行すると、このようなエラーが発生します。しょぼーん。

Error: [ngModel:datefmt] Expected `2015-02-05T00:00:00.000Z` to be a date
 ....

input[type=date]にセットするmodelはDate型でなければならない

公式ドキュメントをよく見ると書いてました。有効なISO-8601フォーマット(yyyy-MM-dd)と書いてますが、Javascript的にはDate型にParseしてセットしなければならないようです。

AngularJS: API: input[date]

WebAPIから受け取る日付はJSONシリアライズされているので、いちいちDate型にPerseしなければならないのか。。。本当に面倒くさい。

現在のベターな回答

いちいちControllerにてParseするのはナンセンスなので、DataService(factory)でParseすることにしました。

ビルトインの$resourceサービスには、受け取ったレスポンスを変形させて後続処理に渡すtransformResponseがあるので、今回はこちらを使ってみました。
(Backbone.ModelやCollectionのparse相当です。)

変更後のDataService的コード

angular.module('app').factory('ggrks', function($resource) {
  return $resource('/api/ggrks', {
    query: {
      transformResponse: transformResponse
    }
  });

  // 受け取ったJSONをObjectにParseする
  function transformResponse(data) {
    data = angular.fromJson(data);
    data.createdAt = new Date(data.createdAt);
    return data;
  }
});

無理やり感がはんぱないですが、、、これで無事、input[type=date]に対して日付型をデータバインドさせることができました。
W3Cの仕様でも、日付として有効な文字列は自動的にParseされるようですし、できればフレームワーク側で判断してPerseして欲しいところですが。。。

これより良いやり方ご存知の方いらっしゃいましたら、ぜひ教えてください。

参考文献

少し古いですがこちらに書かれている、dateInput用のカスタムdirectiveを作成する方法が良さそうな気がします。(未検証)

javascript - Angular.js and HTML5 date input value – how to get Firefox to show a readable date value in a date input? - Stack Overflow