Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular resource make date objects

I'm using $resource to manipulate my data. I would like to convert dates straigt away into date objects when I fetch them. It just makes it easier to work with datepicker etc.

My factory:

AppFactories.factory("Books", ['$resource' ,function($resource){

  return $resource(
        "/books/:id",
        {id: "@id" },
        {
            "update": {method: "PUT", isArray: true },
            "save": {method: "POST", isArray: true },

        }
    );

}]);

Can I create a function within the factory to convert dates from the database into a date object and vice versa when I post/update?

It would be really nice to integrate it right into the factory so that I can reuse it.

like image 278
Tino Avatar asked Feb 02 '14 11:02

Tino


3 Answers

Instead of doing this on every resource you could apply it to the underlying $http as described in more detail here.

app.config(["$httpProvider", function ($httpProvider) {
     $httpProvider.defaults.transformResponse.push(function(responseData){
        convertDateStringsToDates(responseData);
        return responseData;
    });
}]);
like image 71
djskinner Avatar answered Sep 23 '22 07:09

djskinner


The $resource action object can have a property called interceptor, which allows you to define an interceptor object for this particular action (like with $http interceptors). You can define response and responseError properties for this interceptor. This way you can define some date parsing functionality for the actions the resource offers:

function parseResponseDates(response) {
  var data = response.data, key, value;
  for (key in data) {
    if (!data.hasOwnProperty(key) && // don't parse prototype or non-string props
        toString.call(data[key]) !== '[object String]') continue;
    value = Date.parse(data[key]); // try to parse to date
    if (value !== NaN) data[key] = value;
  }
  return response;
}

return $resource('/books/:id', {id: '@id'},
  {
    'update': {
      method: 'PUT', 
      isArray: true, 
      interceptor: {response: parseResponseDates}
    },
    ....
  }
);

Hope this helps!

like image 37
jakee Avatar answered Sep 23 '22 07:09

jakee


I wanted to use Angular.js's ngResource ($resource), but I also wanted my date parsing not to interfere with anything else in $http, and the action's transformResponse option seemed more appropriate than the interceptor option. @jakee's parseResponseDates is nice because it's general purpose, but it's more brute-force than I wanted; I know exactly what fields can be dates in my models, and I only want to parse those fields as dates.

The documentation says the default transformResponse is just angular.fromJson but that's not quite true; it also strips the JSON vulnerability protection prefix, )]}',\n, before running angular.fromJson (http.js source reference). Since $resource doesn't readily expose each action's transformResponse array, you have to handle extending the default transformResponse manually, by injecting $http and then calling $http.defaults.transformResponse.concat(your_transformer). The solution I arrived at, in the context of your example, would look something like this:

AppFactories.factory("Books", function($resource, $http) {
  var raiseBook = function(book) {
    // "created_at" is declared as NOT NULL in the database
    book.created_at = new Date(book.created_at);
    // "completed_at" is a NULL-able field
    if (book.completed_at) {
      book.completed_at = new Date(book.completed_at);
    }
    return book;
  };

  var raiseBooks = function(books) {
    return books.map(raiseBook);
  };

  return $resource("/books/:id", {
    id: "@id"
  }, {
    update: {
      method: "PUT",
      isArray: true,
      // some_array.concat(...) creates and returns a new Array,
      //   without modifying the original some_array Array
      transformResponse: $http.defaults.transformResponse.concat(raiseBooks),
    },
    save: {
      method: "POST",
      isArray: true,
      transformResponse: $http.defaults.transformResponse.concat(raiseBooks),
    },
  });
});

Note that Date.parse(...) returns a Number (milliseconds since the UNIX epoch), so if that's what you want, you'll have to change the new Date(...) calls. But since the original question calls for date objects, new Date(...) is probably what you want.

like image 33
chbrown Avatar answered Sep 22 '22 07:09

chbrown