Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AngularJS / Restangular routing "Cannot set property 'route' of undefined"

I have a AngularJS-based frontend using restangular to fetch records from a Django backend I've built.

I'm making a call for a client list with the following:

var app;

app = angular.module("myApp", ["restangular"]).config(function(RestangularProvider) {
  RestangularProvider.setBaseUrl("http://172.16.91.149:8000/client/v1");
  RestangularProvider.setResponseExtractor(function(response, operation) {
    return response.objects;
  });
  return RestangularProvider.setRequestSuffix("/?callback=abc123");
});

angular.module("myApp").controller("MainCtrl", function($scope, Restangular) {
  return $scope.client = Restangular.all("client").getList();
});

Chrome is showing the backend returning data with an HTTP 200:

abc123({
    "meta": {
        "limit": 20,
        "next": "/client/v1/client/?callback=abc123&limit=20&offset=20",
        "offset": 0,
        "previous": null,
        "total_count": 2
    },
    "objects": [{
        "id": 1,
        "name": "Test",
        "resource_uri": "/client/v1/client/1/"
    }, {
        "id": 2,
        "name": "Test 2",
        "resource_uri": "/client/v1/client/2/"
    }]
})

But once that happens I'm seeing the following stack trace appear in Chrome's console:

TypeError: Cannot set property 'route' of undefined
    at restangularizeBase (http://172.16.91.149:9000/components/restangular/src/restangular.js:395:56)
    at restangularizeCollection (http://172.16.91.149:9000/components/restangular/src/restangular.js:499:35)
    at http://172.16.91.149:9000/components/restangular/src/restangular.js:556:44
    at wrappedCallback (http://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.js:6846:59)
    at http://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.js:6883:26
    at Object.Scope.$eval (http://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.js:8057:28)
    at Object.Scope.$digest (http://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.js:7922:25)
    at Object.Scope.$apply (http://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.js:8143:24)
    at done (http://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.js:9170:20)
    at completeRequest (http://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.js:9333:7) angular.js:5754

I did a breakpoint on line 395 in in restangular.js:

L394    function restangularizeBase(parent, elem, route) {
L395        elem[config.restangularFields.route] = route;

The first time it hits the breakpoint elem is just an object and route has the value of client.

The second time the breakpoint is hit elem is undefined and route has the value of client.

Any ideas why elem would be undefined the second time around?

like image 992
Mark L Avatar asked Jun 25 '13 08:06

Mark L


2 Answers

When requesting lists, Restangular expects the data from the server to be a simple array. However, if the resulting data is wrapped with result metadata, such as pagination info, it falls apart.

If you are using Django REST Framework, it will return results wrapped like this:

{
    "count": 2,
    "next": null,
    "previous": null,
    "results": [
        {
            "id": 1,
            "name": "Foo"
        },
        {
            "id": 2,
            "name": "Bar"
        }
    ]
}

To translate this, you need to create a response extractor function. It's easiest to specify in the module config:

angular.module('myApp', ['myApp.controllers', 'restangular']).
config(function(RestangularProvider) {
    RestangularProvider.setBaseUrl("/api");

    // This function is used to map the JSON data to something Restangular
    // expects
    RestangularProvider.setResponseExtractor(function(response, operation, what, url) {
        if (operation === "getList") {
            // Use results as the return type, and save the result metadata
            // in _resultmeta
            var newResponse = response.results;
            newResponse._resultmeta = {
                "count": response.count,
                "next": response.next,
                "previous": response.previous
            };
            return newResponse;
        }

        return response;
    });
});

This rearranges the results to be a simple array, with an additional property of _resultmeta, containing the metadata. Restangular will do it's thing with the array, and it's objects, and you can access the _resultmeta property when handling the array as you would expect.

like image 67
James Oakley Avatar answered Nov 15 '22 19:11

James Oakley


I'm the creator of Restangular.

The restangularizeBase function is called first for your collection and then for each of your elements.

From the StackTrace, the element is OK, but once the collection is sent to restangularizeBase, it's actually undefined. Could you please console.log response.objects? Also, please update to the latest version.

Also, for the default request parameter, you should be using defaultRequestParams instead of the requestSuffix. requestSuffix should only be used for the ending "/"

Let me know if I can help you some more!

like image 42
mgonto Avatar answered Nov 15 '22 21:11

mgonto