I am new to angular...so I am sure I am doing something wrong. Spent hours trying to search for a solution but the angularjs documentation is next to useless...and pretty much every example out there tries to set this at a global level.
I am trying to pass a complex javascript object as a query prameter. The object has a property that is an array and so I need to flatten (proper term ??) this object so MVC binding can correctly instantiate the model.
The object I am trying to pass is something along the lines of
newRequest = {
SearchTerms: 'error',
PageSize: 25,
...
Facets: [
{ Field: 'createdBy', Value: 'Joe' },
{ Field: 'createdBy', Value: 'Mary' }
]
}
I declared my resource as follows
(function () {
"use strict";
angular
.module('common.services')
.factory('searchResource', ['$resource', 'appSettings', searchResource]);
function searchResource($resource, appSettings) {
return $resource(appSettings.searchPath, null, {
query: {
method: 'GET',
transformRequest: function (data, headersGetter) {
if (data === undefined) { // this is always true
return data;
}
return $.param(data);
}
}
});
}
}());
And I am using it with
vm.executeSearch = function () {
searchResource.query(
newRequest,
function (data) {
vm.response = data;
vm.request = data.Request;
}
);
}
The transformRequest
function is being called...and headersGetter
has a value.
As suggested, I changed direction and instead of using a resource I went with a service via factory. Same result...the data
parameter is undefined. Here is that new code.
(function () {
"use strict";
angular
.module('common.services')
.factory('searchProvider', ['$http', 'appSettings', searchProvider]);
function searchProvider($http, appSettings) {
return {
query: function (request, callback) {
$http({
url: appSettings.searchPath,
method: 'GET',
params: request,
transformRequest: function (data, headersGetter) {
if (data == undefined) {
return data;
}
return $.param(data);
}
})
.success(function (data) {
callback(data);
});
}
}
}
}());
searchProvider.query(
newRequest,
function (data) {
console.log(data);
vm.response = data;
vm.request = data.Request;
}
);
But the problem is that data
is undefined
! I know the newRequest
object is valid because the call goes out. Is just has an improperly formatted url. Where did I go wrong?
The problem is that it's a GET request and ngResource won't accept data
in a GET request:
See https://github.com/angular/angular.js/blob/master/src/ngResource/resource.js#L526
var hasBody = /^(POST|PUT|PATCH)$/i.test(action.method);
It uses hasBody
to determine if to set the data
parameter or not.
So at this point your best bet is to move to using $http since ngResource is for a more RESTful api or switch your API to POST instead of GET.
Edit
And another thing: you don't need to use if (data === undefined)
when you can do : if(!data)
which means the same thing.
Javascript thruthiness : https://developer.mozilla.org/en-US/docs/Glossary/Truthy
Reply to the additional info
You're still getting undefined in data because you're not setting data to anything, you're setting params. But once you're here you don't need to transform the request.
You can just do
$http({
...
params: $.param(request),
...
})
There are a couple bits of extra info that would help us get right at the source of the issue. I think we can get by without them, but in case we get stuck, I would like to know:
appSettings.searchPath
look like?
Just to get things going, I'm going to assume you are not actually getting any console errors, and you've included the necessary libraries for both Angular and jQuery.
If that's the case, here are some tidbits that may help.
When you use the $request
factory, you produce a sort of interface that abstracts making HTTP requests. Since you're using this for "search", let's call this your search interface.
If you perform a GET request via your search interface's query
method, you can pass in any Object or Array and it will build a URL with a query based on that argument.
For example, let's say you passed in your newRequest
variable as-is, without any processing. That would end up producing a URL query like the following (decoded for readability):
?Facets={"Field":"createdBy","Value":"Joe"}&Facets={"Field":"createdBy","Value":"Mary"}&PageSize=25&SearchTerms=error
Notice how a Facets
parameter is specified twice? Once for each element in your Facets
array. This might actually work, if your end-point supports requests in that format.
On the other hand, using jQuery's $.param
utility, as you've attempted to do in this snippet from your first example...
{
query: {
method: 'GET',
transformRequest: function (data, headersGetter) {
if (data === undefined) {
return data;
}
return $.param(data);
}
}
... you would in theory end up with a URL query string like this (decoded for readability):
SearchTerms=error&PageSize=25&Facets[0][Field]=createdBy&Facets[0][Value]=Joe&Facets[1][Field]=createdBy&Facets[1][Value]=Mary
Why "in theory?" Well, as it turns out, you can't use $request
this way, for a few reasons.
Firstly, the HTTP method you specify for query
, will dictate what happens to the parameter object you pass in to that query
method.
Assuming query
uses HTTP GET (as in your examples), the parameter object will be serialized and appended to the end-point URL. You will end up with a URL that has something like this tacked onto it:
?Facets={"Field":"createdBy","Value":"Joe"}&Facets={"Field":"createdBy","Value":"Mary"}&PageSize=25&SearchTerms=error
By using HTTP GET, you are basically saying that the data you want to send with the request is embedded in the URL.
This happens regardless of any processing you specify in transformRequest
.
In fact, since this query
action is not an HTTP POST (it's a GET), there is no data to "post," because that's how GET works differently than POST (read up on HTTP methods, if this is new territory for you). That's why your transformRequest
function gets a data
argument that is undefined
.
Let's assume you set up query
to use HTTP POST. In that case, the parameter object you pass in when calling query
will be put in the post data (aka. request payload) of your HTTP request. It will not be appended to the URL, as had been done with GET.
By using HTTP POST, you are now declaring that the data you want to send can be found in the request body (not the URL).
Since there is "data" to post, the parameters object you pass into query
will be available as the data
argument in your transformRequest
function. This is good, because you can't just send in a raw object like newRequest
. You will need to serialize it into a string. Otherwise you'll end up with post data like:
[object Object]
But since this is now a POST, you don't have to format it like a URL query anymore (since it is not going onto the URL). You can use whatever data format your end-point wants to receive. If you're end-point accepts JSON, you could send a JSON encoded version of your newRequest
object, by running something like this in your transformRequest
function:
return JSON.stringify(data)
Sometimes you have no control over the end-point. What if it only takes GET requests for that search API you want to use? And what if it wants a very specific URL query format?
Not a problem... up to a point.
When you create your search interface via the $request
factory, you can specify a resource URL that is paramterized.
Let's say your search end-point URL was:
/search.do
And it want's to see GET requests with URLs like:
/search.do?PageSize=25&SearchTerms=error
You would then create your search interface with a paramaterized URL, something like this:
$resource("/search.do?PageSize=:PageSize&SearchTerms=:SearchTerms")
Such that you could pass in a request like:
searchResource.query({PageSize: 25, SearchTerms: "error"}, successCallback);
Which would make a GET request with a URL:
/search.do?PageSize=25&SearchTerms=error
Unfortunately this starts to break down with unbounded parameter lists like your Facets
array. The preceding pattern requires that you have a fixed list of arguments.
Let's not forget that if your end-point is not too picky, then you could always just send a GET request with a URL serialized like this:
?Facets={"Field":"createdBy","Value":"Joe"}&Facets={"Field":"createdBy","Value":"Mary"}&PageSize=25&SearchTerms=error
which is what $resource
does to your newRequest
object for free.
What if your really, REALLY want to use $request
, and you have to send GET requests that look like the output of $.param
?
Okay. Let's get fancy. JS fancy.
If you are only ever going to use the query
method on your search interface, you can just wrap the object produced by $resource
.
.factory("searchResource", ["$resource", searchResource]);
function searchResource($resource) {
var searchDelegate = $resource("/search.do?:queryString");
var searchInterface = {
query: function (params) {
var encodedQuery = $.param(params);
return searchDelegate.query({queryString: encodedQuery});
}
};
return searchInterface;
}
Now you can pass in your newRequest
object as-is, and your wrapped search resource will make a GET request to this URL (decoded for readability):
search.do?SearchTerms=error&PageSize=25&Facets[0][Field]=createdBy&Facets[0][Value]=Joe&Facets[1][Field]=createdBy&Facets[1][Value]=Mary
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With