Skip to content
This repository has been archived by the owner on Apr 12, 2024. It is now read-only.

$resource throws TypeError when json has both properties and arrays #1044

Closed
codebreach opened this issue Jun 12, 2012 · 25 comments
Closed

$resource throws TypeError when json has both properties and arrays #1044

codebreach opened this issue Jun 12, 2012 · 25 comments

Comments

@codebreach
Copy link

When using resource on an endpoint which returns json objects of the form:

{ 
  "property1": "value1",
  "property2": "value2",
  "array1": [ {"p1": "v1"}, {"p2": "v2"} ]
}

The resource class throws an error:
TypeError: Object #<Resource> has no method 'push'
at copy (http://code.angularjs.org/angular-1.0.0rc8.js:557:21)
at new Resource (http://code.angularjs.org/angular-resource-1.0.0rc8.js:323:9)
at http://code.angularjs.org/angular-resource-1.0.0rc8.js:379:32
at forEach (http://code.angularjs.org/angular-1.0.0rc8.js:118:20)
at http://code.angularjs.org/angular-resource-1.0.0rc8.js:378:19
at http://code.angularjs.org/angular-1.0.0rc8.js:6349:59
at http://code.angularjs.org/angular-1.0.0rc8.js:6386:26
at Object.$eval (http://code.angularjs.org/angular-1.0.0rc8.js:7444:28)
at Object.$digest (http://code.angularjs.org/angular-1.0.0rc8.js:7316:25)
at Object.$apply (http://code.angularjs.org/angular-1.0.0rc8.js:7527:22)

This is due to the Resource class not taking into consideration that part of the json can be an array. It assumes that the destination for copy is already an array when in reality its a Resource and then the push call fails.

@codebreach
Copy link
Author

This only happens when a Query is done instead of a Get.
This might be expected behavior. Please close the issue in that case.

@AndrewO
Copy link
Contributor

AndrewO commented Jul 10, 2012

I had this same problem. It went away when I had my server return a JSON array at the top level, rather than an object. It makes some sense to assume that .query would normally return an Array, although I can think of situations where I'd want .query to return an object like:

{
  results: [...],
  pagination: {
    prev: http://...,
    next: http://...
  }
}

Looking at the declaration of the default actions for "query", it looks like you can override isArray to false. I haven't tried it myself though.

@mullr
Copy link

mullr commented Aug 18, 2012

I ran in to what appears to be the same issue. The json was an array of objects:

[
  { a: "1", b: "2", c: "3"}, 
  { a: "1", b: "2", c: "3"}
]

It gave me a stack trace like this:

TypeError: Object #<Resource> has no method 'push'
    at copy (http://localhost:3000/javascripts/angular-1.0.1.js:556:21)
    at angular.module.factory.Resource.(anonymous function) (http://localhost:3000/javascripts/angular-resource-1.0.1.js:382:19)
    at deferred.promise.then.wrappedCallback (http://localhost:3000/javascripts/angular-1.0.1.js:6586:59)
    at ref.then (http://localhost:3000/javascripts/angular-1.0.1.js:6623:26)
    at Object.$get.Scope.$eval (http://localhost:3000/javascripts/angular-1.0.1.js:7770:28)
    at Object.$get.Scope.$digest (http://localhost:3000/javascripts/angular-1.0.1.js:7642:25)
    at Object.$get.Scope.$apply (http://localhost:3000/javascripts/angular-1.0.1.js:7856:24)
    at done (http://localhost:3000/javascripts/angular-1.0.1.js:8845:20)
    at completeRequest (http://localhost:3000/javascripts/angular-1.0.1.js:8985:7)
    at <error: Error: INVALID_STATE_ERR: DOM Exception 11> 

This was doing a get().

@sbeam
Copy link

sbeam commented Aug 21, 2012

this is expected behavior. When your JSON returns an array, use query(), if it returns a single object use get() - you can override this by passing isArray true or false as needed, but the default is in line with REST convention

@mullr
Copy link

mullr commented Aug 23, 2012

Ah, that makes sense. I was going to blame the docs, but on second inspection they're perfectly lucid on the matter. Thanks.

@pkozlowski-opensource
Copy link
Member

Looks someone else bumped into the same recently: #1689.
Maybe it would be a good idea to check - if the methods expecting array results - that what coming from the back-end is indeed and array and throw an informative message?

@evictor
Copy link

evictor commented Jan 3, 2013

AndrewO, you can pass metadata in custom response headers, then retrieve them from the header getter function that is passed as the second argument to the success callback.

@bm-w
Copy link

bm-w commented Feb 4, 2013

Ran into @AndrewO's issue as well, where the server response to a list request (so I'm naturally using query) with an object to account for pagination. In my case, Tastypie (a Django REST framework) returns something like {"meta": {"next": ..., etc.}, "objects": [...]}.

Now, I do agree with @evictor's comment that this type of meta data should really be in response headers instead of the response body. But we don't always own the server, and cannot always make sure that responses follow good practices. Perhaps a solution like this would be acceptable (in the function handling the response, in the ngResource module):

if (action.isArray) {
  if (!isArray(data)) {
    var accessor = /* ...some way to get a configurable accessor()... */ || 'objects';
    data = isFunction(accessor) ? accessor(data) : data[accessor];
  }
  //...
}

(Now, my apologies if a solution like this—which deals with others' bad practices—is against Angular's design philosophy.)

@joforiam
Copy link

joforiam commented Feb 5, 2013

Sill having this problem, but actually when executing POSTs (Angular v1.0.4) e.g. -

In the Service:

services.factory('OutlineService', ['$resource', function($resource){
    return $resource('/outline/:action/:id', {}, {
        list: {method:'GET', params:{action:'list'}, isArray:false},
        save: {method:'POST' },
        organize: {method:'POST', params:{action:'organize'}},
        summarize: {method:'POST', params:{action:'summarize'}},
        hierarchy: {method:'GET', params:{action:'hierarchy'}, isArray:false},
        query: {method:'GET', params:{id:'outlineId'}, isArray:false}
    });
}]);

In the controller:

    OutlineService.save({action:'organize'}, outlineGroupings, function(responseData){
        //success handling here
    })

Where 'outlineGroupings' is an array of objects. This all results in:

TypeError: Object # has no method 'push'
at copy (http://skribe.local/global/assets/js/libs/angular/1.0.4/angular.js?1358963974:556:21)
at new Resource (http://skribe.local/global/assets/js/libs/angular/1.0.4/angular-resource.js?1358963974:339:9)

The request doesn't event hit the server, so I don't think it's a response issue in my case.

@evictor
Copy link

evictor commented Feb 5, 2013

@joforiam, your server probably isn't understanding AngularJS's application/json input. Consider trying this technique I outlined in a blog post re: configuring AngularJS to send using x-www-form-urlencoded: http://victorblog.com/2012/12/20/make-angularjs-http-service-behave-like-jquery-ajax/

@joelwreed
Copy link

I am seeing this issue with angular 1.0.5 with a ".get" call that returns an object which has an array as one of its properties.

@joelwreed
Copy link

seems like angular.copy gets into this part of the code where destination is not an array.

if (isArray(source)) {
  destination.length = 0;
  for ( var i = 0; i < source.length; i++) {
    destination.push(copy(source[i]));
  }
} else {

@joelwreed
Copy link

I'm going to try running with this for a while

diff --git public/js/angular.js public/js/angular.js
index 68b33c7..c201241 100644
--- public/js/angular.js
+++ public/js/angular.js

@@ -571,7 +571,7 @@ function copy(source, destination){
     }
   } else {
     if (source === destination) throw Error("Can't copy equivalent objects or arrays");
-    if (isArray(source)) {
+    if (isArray(source) && isArray(destination)) {
       destination.length = 0;
       for ( var i = 0; i < source.length; i++) {
         destination.push(copy(source[i]));

@taem
Copy link

taem commented Mar 7, 2013

I'm having this issue also with AngularJS 1.0.5. My REST method returns following JSON response

[
  [
    "=", "<=", "<", ">=", "<>"
  ],
  null
]

Error message

TypeError: Object #<g> has no method 'push'
    at U (http://localhost:9000/public/javascripts/angular.min.js:10:185)
    at new g (http://localhost:9000/public/javascripts/angular-resource.min.js:7:129)
    at http://localhost:9000/public/javascripts/angular-resource.min.js:8:229
    at Array.forEach (native)
    at n (http://localhost:9000/public/javascripts/angular.min.js:6:192)
    at http://localhost:9000/public/javascripts/angular-resource.min.js:8:206
    at h (http://localhost:9000/public/javascripts/angular.min.js:77:33)
    at http://localhost:9000/public/javascripts/angular.min.js:77:266
    at Object.e.$eval (http://localhost:9000/public/javascripts/angular.min.js:87:347)
    at Object.e.$digest (http://localhost:9000/public/javascripts/angular.min.js:85:198) angular.min.js:61

I'm using query().
Thanks in advance.

@dangersorus
Copy link

Getting this issue when using .query() with array return.

@joelwreed
Copy link

I am having no issues with the patch posted above and don't see this error any more.

@franckverrot
Copy link

@joelwreed your patch fixes the problem on my side too. I'm gonna try to use another resource with get and a custom URL to check if I can get rid of it though. Thanks!

@zbindenren
Copy link

@joelwreed your patch fixes my problem to. I hope this gets into angular.js soon.

Thanks a lot!!!

@sbeam
Copy link

sbeam commented Mar 26, 2013

@joelwreed you should create a pull request with that patch. Seems like everybody runs into this at some point

@martenc
Copy link

martenc commented Mar 27, 2013

+1

Thanks.

@prajwalkman
Copy link

Waiting for this...

@petebacondarwin
Copy link
Contributor

This is not a bug in angular.copy or $resource. You just have to apply isArray correctly in the resource configuration.
By default query expects an array and get expects an object (not an array). If your server provides something different then you override this default: E.g.

{get: {method: 'JSONP', isArray: true}}

or

{query: {isArray:false})

See this example: http://jsfiddle.net/97Uqb/

I haven't seen any examples that are configured correctly and still throw this exception.
@joelwreed - you say you have such an example

I am seeing this issue with angular 1.0.5 with a ".get" call that returns an object which has an array as one of its properties.

Can you post it so that we can check?

The only other thing that could/should be done is to make the error message in $resource more clear. In other words, if you have a resource path that expects an array and gets an object (or vice versa) we throw a suitable error that explains this explicitly, rather than letting it fall through to angular.copy.

petebacondarwin added a commit to petebacondarwin/angular.js that referenced this issue Jun 25, 2013
When using $resource you must setup your actions carefully based on what the server returns. If the server responds to a request with an array then you must configure the action with `isArray:true` and vice versa.  The built-in `get` action defaults to `isArray:false` and the `query` action defaults to `isArray:true`, which is must be changed if the server does not do this.
Before the error message was an exception inside angular.copy, which didn't explain what the real problem was.
Rather than changing the way that angular.copy works, this change ensures that a better error message is provided to the programmer if they do not set up their resource actions correctly.

Closes angular#2255, angular#1044
ksheedlo pushed a commit to ksheedlo/angular.js that referenced this issue Jul 19, 2013
When using $resource you must setup your actions carefully based on what the server returns. If the server responds to a request with an array then you must configure the action with `isArray:true` and vice versa.  The built-in `get` action defaults to `isArray:false` and the `query` action defaults to `isArray:true`, which is must be changed if the server does not do this.
Before the error message was an exception inside angular.copy, which didn't explain what the real problem was.
Rather than changing the way that angular.copy works, this change ensures that a better error message is provided to the programmer if they do not set up their resource actions correctly.

Closes angular#2255, angular#1044
petebacondarwin added a commit to petebacondarwin/angular.js that referenced this issue Jul 30, 2013
When using $resource you must setup your actions carefully based on what the server returns.
If the server responds to a request with an array then you must configure the action with
`isArray:true` and vice versa.  The built-in `get` action defaults to `isArray:false` and the
`query` action defaults to `isArray:true`, which is must be changed if the server does not do this.
Before the error message was an exception inside angular.copy, which didn't explain what the
real problem was. Rather than changing the way that angular.copy works, this change ensures that
a better error message is provided to the programmer if they do not set up their resource actions
correctly.

Closes angular#2255, angular#1044
petebacondarwin added a commit that referenced this issue Jul 31, 2013
When using $resource you must setup your actions carefully based on what the server returns.
If the server responds to a request with an array then you must configure the action with
`isArray:true` and vice versa.  The built-in `get` action defaults to `isArray:false` and the
`query` action defaults to `isArray:true`, which is must be changed if the server does not do this.
Before the error message was an exception inside angular.copy, which didn't explain what the
real problem was. Rather than changing the way that angular.copy works, this change ensures that
a better error message is provided to the programmer if they do not set up their resource actions
correctly.

Closes #2255, #1044
@alanheppenstall
Copy link

I seem to be getting this despite having configured isArray. My configuration is:

DataApp.factory('librarySkillsService', function enrollment($resource){
    return $resource(root_url() + 'library_skill_categories/:id', { id: '@id' }, {
        query: { method: 'GET', isArray: true }
    });
});

My service returns an array of objects each having a property which is an array. Would nested arrays cause this kind of issue?

@joelwreed
Copy link

try my fix alan, does it fix the problem for you?

jr

On 08/08/2013 09:19 PM, alanheppenstall wrote:

I seem to be getting this despite having configured isArray. My configuration is:

DataApp.factory('librarySkillsService', function enrollment($resource){
  return $resource(root_url() + 'library_skill_categories/:id', { id: '@id' }, {
      query: { method: 'GET', isArray: true }
  });
});

My service returns an array of objects each having a property which is an array. Would nested arrays cause this kind of issue?


Reply to this email directly or view it on GitHub:
#1044 (comment)

@alanheppenstall
Copy link

Hi Jr,

No that doesn't seem to work. I still get the Object has no method 'push' error.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging a pull request may close this issue.