Angular.js: ngResource is not suitable for single primitive value(such as string or number) response

Created on 8 Sep 2015  路  7Comments  路  Source: angular/angular.js

the web api(restful most of time) may return a single primitive value as response,such as a string or a number, like this

GET /users/count  ----> 10
GET /users/1/name ------> "Jack"

single primitive value(string or number or true&false and so on) is conform to the specifications of JSON,but now ngResource just support Array or Object response,and I know the reason when I saw the ngResource source code:

........
if (action.isArray) {
  value.length = 0;
  forEach(data, function(item) {
    if (typeof item === "object") {
      value.push(new Resource(item));
    } else {
      // Valid JSON values may be string literals, and these should not be converted
      // into objects. These items will not have access to the Resource prototype
      // methods, but unfortunately there
      value.push(item);
    }
  });
} else {
  shallowClearAndCopy(data, value);
  value.$promise = promise;
}
..........
/**
 * Create a shallow copy of an object and clear other fields from the destination
 */
function shallowClearAndCopy(src, dst) {
  dst = dst || {};

  angular.forEach(dst, function(value, key) {
    delete dst[key];
  });

  for (var key in src) {
    if (src.hasOwnProperty(key) && !(key.charAt(0) === '$' && key.charAt(1) === '$')) {
      dst[key] = src[key];
    }
  }

  return dst;
}

Obviously it can not resolve single string or number response.
What should I do when the response is single string with ngResource?

ngResource low broken expected use bug

Most helpful comment

I agree with @pkozlowski-opensource.

A couple of thoughts:

1.
@kuitos wrote:

from the offical specification of JSON,http://json.org/

A value can be a string in double quotes, or a number, or true or false or null, or an object or an array. These structures can be nested

This snippet is talking about _values_ (wrt to _objects_ and _arrays_). It is never claimed that a single _value_ is valid JSON. It says that JSON is based on _objects_ and _arrays_ (which isn't very clear either).

2.
More importantly, $resource is not supposed to accept any valid JSON (because it wouldn't necessarily know what to do with it). It creates and acts upon resource objects and doesn't know how to handle primitives.

3.
$resource is targeting specific usecases. It's pretty good for simple interaction with RESTful data sources (which is more than enough for 90% of the time according to my expecrience), but is obviously neither as flexible as other alternatives or custom implementations (wrappers around $http), nor suitable for all usecases.

BTW, you can easily create a wrapper around $resource, that adds some "non-$resource-like", static methods to the created resource class; e.g.:

// Create a "countable" `User` resource class: 
.service('User', function UserFactory($http, $resource) {
  var BASE_URL = '/api/users/';

  var User = $resource(BASE_URL + ':userId', {userId: '@id'});
  User.count = $http.get.bind($http, BASE_URL + 'count');

  return User;
}

// User `User`:
function AppCtrl(User) {
  // Request: User-count
  User.count().then(...);

  // Request: User 1
  var user1 = User.get({userId: 1});
}

Demo

All 7 comments

Now I have to overwrite the shallowClearAndCopy method to support my business

function shallowClearAndCopy(src, dst) {
  dst = dst || {};

  angular.forEach(dst, function(value, key) {
    delete dst[key];
  });

  if (angular.isString(src) || angular.isNumber(src)) {
      dst._data = src;
    } else {
      for (var key in src) {
        if (src.hasOwnProperty(key) && !(key.charAt(0) === '$' && key.charAt(1) === '$')) {
          dst[key] = src[key];
        }
      }
    }

  return dst;
}

what it do is to wrap the single value response to {_data:xxx},and then I can get the value by resource._data.
and then override the $resource service use $provide.decorator.
It is so ugly but I do not have any other ideas

Are you saying that "string" and 4434 are valid JSOn responses? I don't think that is the case.

Ok, it looks like it is possible to have single values as valid JSON, see http://tools.ietf.org/html/rfc7159 and http://stackoverflow.com/questions/13318420/json-standard-a-single-string-value

I'd consider it bad practice though to have an API return this. I'd also expect not many people to write their JSON this way. If there's a PR maybe we'll consider it.

from the offical specification of JSON,http://json.org/

A value can be a string in double quotes, or a number, or true or false or null, or an object or an array. These structures can be nested

yes response a single value is in a few case usually,but I don`t think it is a bad practice.Such as a api for invoker to get a username or a total count,why I need to wrap the response as a object but not the original primitive value?I think it is redundant.
So I have to give them a _value(or other key) key to wrap them as an object and I think it is the only way to compatible with the primitive format of JSON response under the existing design of ngResource.

IMO we should not try to fix this one. Returning primitives from $resource call doesn't make sense since we need a resource should be an object (so it can be enriched with $resource-specific methods). I've got an impression that fixing this would encourage people even more to use $resource as a general-purpose XHR call. This is not what it was designed for and $http should be used instead. Using $resource to fetch a single value is wasteful.

I agree with @pkozlowski-opensource.

A couple of thoughts:

1.
@kuitos wrote:

from the offical specification of JSON,http://json.org/

A value can be a string in double quotes, or a number, or true or false or null, or an object or an array. These structures can be nested

This snippet is talking about _values_ (wrt to _objects_ and _arrays_). It is never claimed that a single _value_ is valid JSON. It says that JSON is based on _objects_ and _arrays_ (which isn't very clear either).

2.
More importantly, $resource is not supposed to accept any valid JSON (because it wouldn't necessarily know what to do with it). It creates and acts upon resource objects and doesn't know how to handle primitives.

3.
$resource is targeting specific usecases. It's pretty good for simple interaction with RESTful data sources (which is more than enough for 90% of the time according to my expecrience), but is obviously neither as flexible as other alternatives or custom implementations (wrappers around $http), nor suitable for all usecases.

BTW, you can easily create a wrapper around $resource, that adds some "non-$resource-like", static methods to the created resource class; e.g.:

// Create a "countable" `User` resource class: 
.service('User', function UserFactory($http, $resource) {
  var BASE_URL = '/api/users/';

  var User = $resource(BASE_URL + ':userId', {userId: '@id'});
  User.count = $http.get.bind($http, BASE_URL + 'count');

  return User;
}

// User `User`:
function AppCtrl(User) {
  // Request: User-count
  User.count().then(...);

  // Request: User 1
  var user1 = User.get({userId: 1});
}

Demo

@gkalpak
Thanks for your correction,I realized my mistake just a moment ago.
Now the implementations are compatible with the primitives like JSON.parse and the new version RFC7159 was changed to suit for primitives(in @pkozlowski-opensource answer),although I don`t know why it made this change.

Maybe now I should communicate with api designer to fix it.馃槃

Was this page helpful?
0 / 5 - 0 ratings

Related issues

jtorbicki picture jtorbicki  路  3Comments

kishanmundha picture kishanmundha  路  3Comments

ceymard picture ceymard  路  3Comments

nosideeffects picture nosideeffects  路  3Comments

WesleyKapow picture WesleyKapow  路  3Comments