AngularJS and complex JSON returned by django tastypie
Asked Answered
C

3

10

I have few resources written on AngularJS that access a Tastypie API. Everything works fine, except for a detail: tastypie always encapsulate the actual result inside a objects attribute on a JSON, example:

/api/v1/reminder/:

{
    meta: {
        limit: 20,
        next: null,
        offset: 0,
        previous: null,
        total_count: 3
    },
    objects: [{
        category: {
            color: "#999999",
            id: 1,
            name: "Groceries",
            resource_uri: "/api/v1/category/1"
        },
        description: "",
        due_date: "2010-10-16",
        id: 1,
        repeat: "weekly",
        resource_uri: "/api/v1/reminder/1",
        value: "-50"
    }, {
        category: {
            color: "#999999",
            id: 1,
            name: "Groceries",
            resource_uri: "/api/v1/category/1"
        },
        description: "",
        due_date: "2010-10-17",
        id: 2,
        repeat: "weekly",
        resource_uri: "/api/v1/reminder/2",
        value: "-50"
    }
}

It was wasy to fix using a callback to the get() call:

Reminder.get().$then(function (result) {
    $scope.reminders  = result.data.objects;
});

But I know result.resource is an actual Reminder instance.

.factory('Reminder', ['$resource', function($resource){
    var Reminder = $resource('/api/v1/reminder/:id', {}, {
        get: {
            method: 'GET',
            isArray: false
        }
    });

    Reminder.prototype.TESTE = function () {console.log('asd');};

    return Reminder;
}])

Now I need to implement behavior on my Reminder class, and I need every element on my meta.objects to be an instance of Reminder:

Reminder.get().$then(function (result) {
    $scope.reminders  = result.data.objects;

    result.resource.TESTE(); // -> outputs 'asd'

    o = result.data.objects[0];
    o.TESTE // -> undefined, obvisously
    i = new Reminder(o);
    i.TESTE() // -> outputs 'asd'
});

So, how to I get angularjs to understand that every object on objects is the actual result so it behaves like a list of instances?

The workaround is to creating a new list iterating on the results creating the instances, but it's not optimal...

Suggestions?

Solution by @rtcherry:

As suggested by rtcherry, I used restangular

Configuring the reading of request data:

.config(['RestangularProvider', function(RestangularProvider) {
    RestangularProvider.setBaseUrl("/api/v1");

    RestangularProvider.setResponseExtractor(function(response, operation, what, url) {
        var newResponse;
        if (operation === "getList") {
            newResponse = response.objects;
            newResponse.metadata = response.meta;
        } else {
            newResponse = response.data;
        }
        return newResponse;
    });
}])

Loading the reminders:

function RemindersCtrl ($scope, $rootScope, Reminder) {
    $scope.reminders = Reminder.getList();
}

Adding my custom method to Reminder (not as clean as ngResource, but doable):

.factory('Reminder', ['Restangular', '$filter', function(Restangular, $filter){
    var Reminder = Restangular.all('reminder');

    var remainingDays = function () {
        //do stuff
    };

    // adding custom behavior
    Restangular.addElementTransformer('reminder', false, function (reminder) {
        reminder.remainingDays = remainingDays;
        return reminder;
    });

    return Reminder;
}])

Solution by @moderndegree:

I used pure ngResource:

var tastypieDataTransformer = function ($http) {
    return $http.defaults.transformResponse.concat([
        function (data, headersGetter) {
            var result = data.objects;
            result.meta = data.meta;
            return result;
        }
    ])
};

...

.factory('Reminder', ['$resource', '$http', function($resource, $http){
    var Reminder = $resource('/api/v1/reminder/:id', {}, {
        query: {
            method: 'GET',
            isArray: true,
            transformResponse: tastypieDataTransformer($http)
        }
    });

    Reminder.prototype.remainingDays = function () {
        // doing stuff
    };

    return Reminder;
}])

My controller:

Transaction.query(filter).$then(function (result) {
    $scope.days = [];
    var transactions = result.resource;
    resource[0].remainingDays(); // it works

});
Corinecorinna answered 26/6, 2013 at 0:59 Comment(5)
You may want to try something like restangular.Heeling
whoa, seems good.. i will look into itCorinecorinna
done. it's not as easy as adding a method to a angular resource, but can be done.. but restangular pays off on it's many useful features. do you want to make an answer out of your comment so I can mark it?Corinecorinna
Hey, I'm the creator of Restangular. If you have to choose, which one do you want better ngResource or Restangular for all your usage? And why? Thanks for the information to make Restangular better :)Anatomy
Is the solution by @moderndegree working in 1.2.12? I am trying to return list of object and include a meta element to presever the total_pages (to avoid yet another request just for that). It seems even though I transformResponse exacactly like in the example, the meta is not available later on in the returned object.Ocana
E
6

If you wanted to avoid using an additional library, you should be able to do the following:

$resource('/api/v1/reminder/', {}, {
    query: {
        method: 'GET',
        isArray: true,
        transformResponse: $http.defaults.transformResponse.concat([
            function (data, headersGetter) {
                return data.objects;
            }
        ])
    }
});

This will append your transform to $HttpProvider's default transformer.

Note: Correct me if I'm wrong on this one but I believe this feature requires v1.1.2 or greater.

Estriol answered 27/6, 2013 at 1:2 Comment(4)
+1 for posting a solution that uses the newer ngResource features from the 1.1.x branch of AngularJS.Heeling
i'm going to try it! i'm having some trouble with restangular not giving me the response on a post request, and it worked on ngresource. reverting to the later would save me some time.Corinecorinna
Cool. It is always good to limit the number of libraries you have to load. Also, here is an answer I posted recently that solved a similar problem. #17333422 @Shaman was looking for a way to transform the response before the default transform.Estriol
it works... I had to save the meta as a property on the objects array for later use.. I will now find a way to set this on all my resources (but not globally)Corinecorinna
H
4

You may want to try something like restangular.

There is some configuration needed to make that work. An example is here.

Heeling answered 26/6, 2013 at 9:48 Comment(0)
E
3

I ended up doing the following in order to preserve the meta object directly on the results returned by Reminder.query() (expanding on the answer from @moderndegree).

var Reminder = $resource('/api/v1/reminder/', {}, {
  query: {
    method: 'GET',
    isArray: true,
    transformResponse: $http.defaults.transformResponse.concat([
      function (data, headersGetter) {
          return data.objects;
      }
    ]),
    interceptor: {
      response: function(response) {
        response.resource.meta = response.data.meta;
      }
    }
  }
});

That allows you to get the meta value directly on the returned object:

var reminders = Reminder.query(...);
console.log(reminders.meta); // Returns `meta` as expected.

I think it would also be possible to do something similar inside the callback from Reminder.query since the response object is available there as well.

Episode answered 3/10, 2014 at 20:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.