AngularJS : $resource, response Object or Array [duplicate] - angularjs

There are two ways I can GET a REST resource by ID:
GET /users/1
GET /users/1,2
The first returns a single object like {id:1,name:"John"} while the second returns an array like [{id:1,name:"John"},{id:2,name:"Jill"}].
Please, no arguments about whether or not this is legit REST; just accept that a service has this and I need to work with it.
angular's $resource actually intelligently handles it from the request side:
User.get({users:['1','2']})
transforms into
GET /users/1,2
But it doesn't handle the response well. It expects a single object. If I change the definition to isArray:true, then it fails on a single request GET /users/1
How do I get it to intelligently handle both?
EDIT: I did some weird hacking to get it to work, but would prefer a native method:
factory('Resource',['$resource','_',function ($resource,_) {
return function(url,params,methods){
var defaults = {
getSingle: {method: 'get', isArray:false},
getMultiple: {method: 'get', isArray:true}
}, resource = $resource(url,params,angular.extend(defaults,methods)), urlParams = {};
_.each(url.split(/\W/), function(param){
if (param && (new RegExp("(^|[^\\\\]):" + param + "\\W").test(url))) {
urlParams[param] = true;
}
});
// do not override if they did
if (!(methods||{}).get) {
resource.get = function (params,success,error) {
// look for multiples
var isMultiple = false;
_.each(params,function (val,key) {
if (key && urlParams[key] && angular.isArray(val)) {
isMultiple = true;
return(false);
}
});
return this[isMultiple?"getMultiple":"getSingle"](params,success,error);
};
}
return(resource);
};
}]).

The normal convention is to create a Resource.get() method for single objects, and a Resource.query() method for arrays of them.

I had a similar issue with the WP-API when trying to do a PUT request to a post. Currently that API returns an object representing the post if everything went okay, but if there is an error (eg the authorization credentials don't match) then it returns an array of errors. So I was getting the error Error: [$resource:badcfg] Error in resource configuration. Expected response to contain an object but got an array.
I managed to find a solution by using the transformResponse property on my custom action object. I define a function which inspects the response and then if it is an array, it converts the array into an object. This seems to work okay, and seems a bit less complex than the solution you posted in your update:
var wpPost = $resource(PATH_TO_WORDPRESS_API + 'posts/:id', {},
{
'update': {
method: 'PUT',
params: {id: '#id'},
transformResponse: function(data) {
var response = angular.fromJson(data);
if (response.length) {
// the response is an array, so convert it into an object
var object = {};
for( var i = 0; i < response.length; i ++) {
object[i] = response[i];
}
return object;
} else {
return response;
}
}
}
});

Related

How to access several $http.get() and store their responses in an array using for loop and access the array

My code is like below,
// calls only the API and return it
s.getArchSales = function (url, qParam) {
// set the request object
var req = {
'method': 'POST',
'headers': securitySrv.header, // set the header
'url': url,
'data': qParam
}
return $http(req)
}
var portFolioMixArray = []
for(var i = 0; i < tech.length; i++ ){
s.getArchSales(url, query)
.then(function (response) {
portFolioMixArray.push(response.data)
})
}
tech is also an array which is also computed dynamically
Now when I console.log(portFolioMixArray) it shows Array[0] with an extension symbol at the left like below,
I can't access the Array elements. How can I access it ??
When you did console.log, at that time the http response was not yet received and the array was empty and hence you were not able to access the array elements.
Later, the response was received and array was updated. That's why you see the blue icon with the message.
You can get it by using portFolioMixArray[0]
if you want bind the values in html, then you need use ng-repeat
You should use the object in $scope
like
$scope.portFolioMixArray = []
for(var i = 0; i < tech.length; i++ ){
s.getArchSales(url, query)
.then(function (response) {
$scope.portFolioMixArray.push(response.data)
})
}
//html
<div ng-repeat="item in portFolioMixArray">{{item .YourObjectNmae}}</div>
I think you should understand the concept of promise, in Angular you can use $q.(https://docs.angularjs.org/api/ng/service/$q)
You can access your array like this:
var promises = tech.map(function (item) {
return s.getArchSales(url, query).then(function (response) {
portFolioMixArray.push(response.data)
})
}
$q.all(promises).then(function () {
console.log(portFolioMixArray);
}

Query: Response does not match configured parameter

i am getting response error please any one try to solve my problem. i am trying to solve that but i didn't solve this. please help me.
thanks in advance
$scope.init = {};
var Call = $resource('../api/Home', {}, { query: { method: 'POST', isArray: false} });
$scope.init = function () {
//alert("hi");
$scope.selected = "";
var x = $('#txtSearch').val();
var _ReqObj = new Object();
_ReqObj.id = x;
_ReqObj.Meth = "GJ";
Call.query({}, _ReqObj,
function (response) {
alert(response);
alert(_ReqObj);
if (response == '') {
// alert('no data');
window.location.replace("index.html");
}
else {
//$scope.click = response;
$scope.init = response;
}
},
function (error) {
window.location.replace("index.html");
}
);
};
The error message says: "Expected response to contain an object but got an array"
this means: Your request (Call.query) does expect a single object (you are setting isArray: false). But the server sends an array. So the server is not sending what the function expects!
There is some hints I want to give you:
Why are you using query? A query is normally used to get an array from the server, not a single object. Why are you using query at all?
Do you really want a single object or do you want a list of objects?
What is the server sending? Open the development console in your browser (ctr+alt+I in Chrome) and click on the Network tab. Click on the request (../api/Home) and check the response from the server. You should see the json objects or arrays the server sent as a response to your request.

Angularjs: Transform data before send with ng-resource and transformRequest

I want to change some data before sending it to the server via ng-resource. I use the tranformRequest-Function like this:
update: {
method: 'PUT',
transformRequest: function (data) {
// modify data then
return data;
}
}
I can modify data this way but in the request my data is always serialized. I want keep my data as JSON. Is this possible with transformRequest or have this to be done in controller. I would prefer to do it in the service. Thx for help
Omg i feel like an idiot. You simply have to do
update: {
method: 'PUT',
transformRequest: function (data) {
// modify data then
return angular.toJson(data);
}
}
Here's an example I'm using in my app. Callers of the $resource methods pass in a simple list of parameters as JSON, and the transformRequest bundles the parameters into the format expected by the API I'm using.
var myServices = angular.module('myServices', ['ngResource']);
...
myServices.factory('MyServer', ['$resource', function($resource){
var formatLoginRequest = function(data) {
// input looks like: {username:"imauser", pw:"password"}
// output should be: {request: {cmd:"login", username:"imauser", pw:"password"}}
data.cmd="login";
data = {request: data};
data = angular.toJson(data);
return data;
};
...
return = $resource('https://api.server.com/', {}, {
login: {method:'POST', params:{}, isArray:false, transformRequest:formatLoginRequest },
...other methods defined here...
});
As noted elsewhere, angular.toJson() won't correctly serialize all data types, but it is sufficient for my case with JSON.
In case somebody else comes across this, Angular provides default transformations for objects. The $http service exposes defaults.transformRequest, which checks if the data property is an object and automatically serializes it to JSON.
For this particular case, i would do a simple check if data is an object and if not make it one and override the $http.defaults.transformRequest.
function appendTransform(defaults, transform) {
defaults = angular.isArray(defaults) ? defaults : [defaults];
return defaults.concat(transform);
};
update: {
method: 'PUT',
transformRequest:
appendTransform($http.defaults.transformResponse,function(data) {
data = angular.isObject(data) ? data : {data};
return data;
})
}
Yes it is. A bit troublesome and ulgy but here it is :
// from angular-resource
var toString= function() {
var value = [];
_.forEach(this, function(e) {
value.push('' + e);
});
return '[' + value.join(', ') + ']';
};
var isObject = function isObject(value) {
// http://jsperf.com/isobject4
return value !== null && typeof value === 'object';
};
var isFile = function(obj) {
return toString.call(obj) === '[object File]';
}
var isFormData = function(obj) {
return toString.call(obj) === '[object FormData]';
}
var isBlob = function(obj) {
return toString.call(obj) === '[object Blob]';
}
var defaultToJson = function(d) {
return isObject(d) && !isFile(d) && !isBlob(d) && !isFormData(d) ? angular.toJson(d) : d;
};
this.typeServiceProcessData = function(d){
//[put your code here for to change data this will be called before angular default serialization to the server]
};
this.typeServiceProcessJsData = function(d){
//[for data that come back from the server after getting parse by default angular json parsing]
};
// sample creation of a resource method, be really carefull about the order in transformResponse and transformRequest
'get': {method:'GET', transformResponse:[$http.defaults.transformResponse[0], this.typeServiceProcessData]},
'create': {method:'POST', url:baseResourceUrl, transformRequest:[this.typeServiceProcessJsData, defaultToJson]},
It's a big huge, it's a code i done some time ago and i copy/pasted some function definition from angular-resource because they weren't define in that scope and weren't accessible from outside of angular resource. To see why they're needed check the defaultToJson function i defined which is the angular default.
If someone has a better way to just copy paste that bunch of function i take too :)

angularjs resource limit changes after first call

Problem description
Im using the angular resource to get data from my server. I've extended it a bit to make sure all of my resources have security headers.
Problem is that on the second get request and on, my get requests are sent with limit=0, and only the first get request is sent correctly (with limit=12).
Code part
This is my base resource factory (for making sure all resource contain the keys and everything):
app.factory('SecuredFactory', function($resource){
var DEFAULT_ACTIONS = {
'get': {method:'GET'},
'query': {method:'GET', isArray:true},
};
var DEFAULT_PARAMS = {
'limit': 12,
'format': 'json'
};
for(var key in DEFAULT_ACTIONS){
DEFAULT_ACTIONS[key]['headers'] = <headers object>;
}
var securedResource = function(url, paramDefaults, actions){
for (var attrname in actions) {
DEFAULT_ACTIONS[attrname] = actions[attrname];
}
for (var attrname in paramDefaults) {
DEFAULT_PARAMS[attrname] = paramDefaults[attrname];
}
var defaultResource = $resource(url, DEFAULT_PARAMS, DEFAULT_ACTIONS);
return defaultResource;
};
return securedResource;
});
And this is an example of how I creat a specific factory out of the secured one:
app.factory('QuestionFactory', function(SecuredFactory, Constants){
var url = Constants.SERVER_URL + 'question/';
var Task = SecuredFactory(url);
return Task;
});
And this is finally how I use it, for example:
// filtering example (not important for this matter):
var filtering = {author: "Daniel"};
var contents = [];
var resource = QuestionFactory;
resource.get(filtering, function (res) {
// success fetching
$scope.contents = $scope.contents.concat(res['objects']);
}
// failed fetching
, function (err) {
}
);
The requests
first request:
question?format=json&limit=12&offset=0
second request and on:
question?format=json&limit=0&offset=0
My problem was that the DEFAULT_PARAMS variable was declared as global. I didn't realize that invoking the secured factory with {limit: 0} will override the global, therefore changing the limit to 0 for ALL of my resources.
Changing the securedFactory to a service and moving the "globals" into the returned function solved it. Had to add new ofcourse before every securedService call.

Can Angular $resource.get handle both array and non-array for GET?

There are two ways I can GET a REST resource by ID:
GET /users/1
GET /users/1,2
The first returns a single object like {id:1,name:"John"} while the second returns an array like [{id:1,name:"John"},{id:2,name:"Jill"}].
Please, no arguments about whether or not this is legit REST; just accept that a service has this and I need to work with it.
angular's $resource actually intelligently handles it from the request side:
User.get({users:['1','2']})
transforms into
GET /users/1,2
But it doesn't handle the response well. It expects a single object. If I change the definition to isArray:true, then it fails on a single request GET /users/1
How do I get it to intelligently handle both?
EDIT: I did some weird hacking to get it to work, but would prefer a native method:
factory('Resource',['$resource','_',function ($resource,_) {
return function(url,params,methods){
var defaults = {
getSingle: {method: 'get', isArray:false},
getMultiple: {method: 'get', isArray:true}
}, resource = $resource(url,params,angular.extend(defaults,methods)), urlParams = {};
_.each(url.split(/\W/), function(param){
if (param && (new RegExp("(^|[^\\\\]):" + param + "\\W").test(url))) {
urlParams[param] = true;
}
});
// do not override if they did
if (!(methods||{}).get) {
resource.get = function (params,success,error) {
// look for multiples
var isMultiple = false;
_.each(params,function (val,key) {
if (key && urlParams[key] && angular.isArray(val)) {
isMultiple = true;
return(false);
}
});
return this[isMultiple?"getMultiple":"getSingle"](params,success,error);
};
}
return(resource);
};
}]).
The normal convention is to create a Resource.get() method for single objects, and a Resource.query() method for arrays of them.
I had a similar issue with the WP-API when trying to do a PUT request to a post. Currently that API returns an object representing the post if everything went okay, but if there is an error (eg the authorization credentials don't match) then it returns an array of errors. So I was getting the error Error: [$resource:badcfg] Error in resource configuration. Expected response to contain an object but got an array.
I managed to find a solution by using the transformResponse property on my custom action object. I define a function which inspects the response and then if it is an array, it converts the array into an object. This seems to work okay, and seems a bit less complex than the solution you posted in your update:
var wpPost = $resource(PATH_TO_WORDPRESS_API + 'posts/:id', {},
{
'update': {
method: 'PUT',
params: {id: '#id'},
transformResponse: function(data) {
var response = angular.fromJson(data);
if (response.length) {
// the response is an array, so convert it into an object
var object = {};
for( var i = 0; i < response.length; i ++) {
object[i] = response[i];
}
return object;
} else {
return response;
}
}
}
});

Resources