Pregunta

I have to build an angularjs client for an API outputting JSON like this:

{
  "count": 10,
  "next": null,
  "previous": "http://site.tld/api/items/?start=4"
  "results": [
    {
      "url": "http://site.tld/api/items/1.json",
      "title": "test",
      "description": "",
      "user": "http://site.tld/api/users/4.json",
      "creation_datetime": "2013-05-08T14:31:43.428"
    },
    {
      "url": "http://site.tld/api/items/2.json",
      "title": "test2",
      "description": "",
      "user": "http://site.tld/api/users/1.json",
      "creation_datetime": "2013-05-08T14:31:43.428"
    },
    {
      "url": "http://site.tld/api/items/3.json",
      "title": "test3",
      "description": "",
      "user": "http://site.tld/api/users/2.json",
      "creation_datetime": "2013-05-08T14:31:43.428"
    }
  ]
}

How can I make a $resource that maps to this ? If I use isArray=false, I'll get the entire blob as one object, usable for reading, but I can't call .put() on it. If I use isArray, it just doesn't work.

Is there any clean way to do this? Or should I go back to using $http?

¿Fue útil?

Solución

You have a couple of options. If you can change the server output you could add the meta info (count, next, previous) as header values instead of adding them in the response body.

Your second option is to transform the response with transformResponse. This is available as a $response configuration in angular v1.1.2 and later (the unstable branch):

var Data = $resource('./data.json',{},{
  list:{isArray:true,method:'get',
    transformResponse: function (data, headers) {
      return JSON.parse(data).results; 
   }}
});

If you don't want to use the unstable branch it is also possible to change the $http which $resource uses:

$http.defaults.transformResponse.push(function(data){
  if(data && data.results){
    return data.results;
  }
});

I've created a plunker with both examples: http://plnkr.co/edit/PmYotP0eo5k41Z6ZCxl4?p=preview

I'm not sure what the best approach for passing on the meta data to the rest of your application (if you need it). You could append it to the first result, or add it as a separate object - maybe not so elegant, but it'll get the job done.

Otros consejos

I know this question is little bit old. But I think answer doesn't cover main problem - how to get pagination information and how to keep Resource features for list of objects.

You have basicly two solutions, pass paginator data into headers in transform message or use $http and then instantiate items manually.

1.Transform message

Here i redefine query to put pagination data into headers.

Headers is not an array - it is "headersGetter" which returns header by calling headers('Header-Name') and returns inner object by calling headers(). I have to set header lowercase.

var PAGINATION_TOTAL = 'pagination-total-elements';
var PAGINATION_SIZE = 'pagination-size';

...

.factory('BeerResourceFactory', function($resource, API_URI) {
        return $resource(API_URI + '/beer/:id',
            {'id': '@id'},
            {
              'update': {method: 'PUT'},
              'query' : {method: 'GET', isArray:true, transformResponse : function(data, headers) {
                var jsonData = JSON.parse(data);
                headers()[PAGINATION_TOTAL] = jsonData.totalElements;
                headers()[PAGINATION_SIZE] = jsonData.size;

                return jsonData.content;
              }}
            });
      })

After that I define service which encapsulate this and take pagination from headers. Sudenly we cannot use $promise.then() and retrurn result because promise gets only result as argument and not the headersGetter, so we have to use ordinary callback and create own promise.

.service('beerService', function(BeerResourceFactory, $q) {
    this.query = function(filter) {

          var defer = $q.defer();

          BeerResourceFactory.query(filter, function(result, headers) {
            var promiseResult =  {
              beers: result,
              paging: {
                totalItems: headers(PAGINATION_TOTAL),
                perPage: headers(PAGINATION_SIZE)
              }
            };

            defer.resolve(promiseResult);
          });

          return defer.promise;
    }

2.Using $http and instantiate Resource

When using $http instead of resource, there is problem that you still want to use elements of array as resource instances and be able to call $save / $delete, so it is possible to instantiate them manually. Here you can also use ordinary promise as ussual.

.service('beerService', function($http, BeerResourceFactory, API_URI) {
    this.query = function(filter) {
        return $http.get(API_URI + '/beer', {params: filter})
              .then(function(response) {

                var transformedList = response.data.content.map(function(element) {
                  return new BeerResourceFactory(element);
                });

                return {
                  beers: transformedList,
                  paging: {
                    totalItems: response.data.totalElements,
                    perPage: response.data.size
                  }
                };
              });
         };

I would prefer second solution, because its more simple.

I stumped into this problem as well, and here's what worked for me. Add a response transformer which would take the array result and create a resource object manually, which I assume ngResource would do internally anyway.

  var Plan =  $resource(apiPath + 'plans/:planId/', {planId:'@id'}, {
    query: {
      isArray: false,
      transformResponse: function(response){
        response = angular.fromJson(response);
        response.results = response.results.map(function(plan) {
          return new Plan(plan);
        });
        return response;
      }
    },
  });

By now this is even older, but I managed to solve this in a single resource factory:

.factory('BeerResourceFactory', function($resource, API_URI) {
    var resource = $resource(API_URI + '/beer/:id',
        {'id': '@id'},
        {
          'update': {method: 'PUT'},
          'query' : {method: 'GET', isArray:true, transformResponse : function(data) {
            var jsonData = angular.fromJson(data);
            jsonData.beers = jsonData.beers.map(function (beer) {
                return new resource(beer)
            });

            return jsonData.content;
          }}
        });
      return resource;
  })
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top