I'm using Angular 5 with HttpInterceptors.
I already know I can get each value I want from HttpParams via several methods.
Also - If I want to see all values , I can use the .toString() method
params = new HttpParams()
.set('page', '2')
.set('sort', 'name');
console.log(params.toString()); //Returns page=2&sort=name
But in my case I send json objects as parameters :
{
a:1 , b:[1,2,3] , c:[{...}]
}
I'm using interceptors to log the request parameters , but when I JSON.stringify(req.Params) , I get :
Params={
"updates": null,
"cloneFrom": null,
"encoder": {},
"map": {}
}
Which doesn't expose the values.
I don't want to see the parameters as a regular form post parameters -( it will be very unclear), but as an object as I've sent it.
Question:
How can I extract the parameters from the request object in the interceptor , but as json format :
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>>
{
JSON.stringify( req.params) // <--- ?? doesn't yield the params.
}
If you don't like method toString() that returns an encoded string, where key-value pairs (separated by =) are separated by &s you can write your own method that will transform data stored in Map in some data you want to use.
For example:
const params = new HttpParams()
.set('page', '2')
.set('sort', 'name');
const paramsArray = params.keys().map(x => ({ [x]: params.get(x) }));
console.log(JSON.stringify(paramsArray));
It's similar to the approach that is used in toString method
https://github.com/angular/angular/blob/master/packages/common/http/src/params.ts#L177-L186
Ng-run Example
What you actually requested was a JSON Object. So this is how you get such an object:
const params = new HttpParams()
.set('page', '2')
.set('sort', 'name');
const paramsObject = params.keys().reduce((object, key) => {
object[key] = params.get(key)
return object
}, {})
console.log(paramsObject)
// And json if you really want
const json = JSON.stringify(paramsObject)
Related
I have multiples URLSearchParams created from object mainly because it's lighter to write and to read but for some of them, I need multiple values for a "variable" like this : foo=bar&foo=baz.
For now I do it with .append but it's heavy to read having multiple lines pretty identical.
Is there a way to do it from the constructor, with an object ?
let params;
// Currently used (and working) code
params = new URLSearchParams()
params.append("foo", "bar");
params.append("foo", "baz");
console.log(params.toString()); //Wanted result but heavy to read with more values
// Wanted code (but not the wanted result for now)
params = new URLSearchParams({
"foo": ["bar", "baz"]
});
console.log(params.toString());
params = new URLSearchParams({
"foo": "bar",
"foo": "baz"
});
console.log(params.toString());
The URLSearchParams can takes an init value as argument for its constructor containing the following:
One of:
A string, which will be parsed from application/x-www-form-urlencoded format. A leading '?' character
is ignored.
A literal sequence of name-value string pairs, or any object — such as a FormData object — with an iterator that produces a sequence of
string pairs. Note that File entries will be serialized as [object File] rather than as their filename (as they would in an
application/x-www-form-urlencoded form).
A record of string keys and string values.
In your case the second one seems to work the best and can be done like this:
const searchParams = new URLSearchParams([['foo', 'bar'],['foo', 'baz'],['foo', 'qux']])
console.log(searchParams.toString())
If you want to deal with objects, you can create your own structure and use a function to create the wanted data
For example :
const params = [
{name: "foo", values: ["bar", "baz", "qux"]},
{name: "bar", values: ["foo", "foo2", "foo3"]},
]
const initParams = (params) => params.reduce((acc, curr) => {
const arr = curr.values.map(x => [curr.name, x])
return acc.concat(arr)
}, [])
const searchParams = new URLSearchParams(initParams(params))
console.log(searchParams.toString())
I would introduce a function to build the thing you need (URLSearchParams) from the structure you want (an object containing strings or arrays of strings), pushing the "heavy" details down below an interface you own. A simple implementation would just use .append as appropriate:
// For TypeScript (e.g. Angular) users:
// function createSearchParams(params: { [key: string]: string | string[] }): URLSearchParams {
function createSearchParams(params) {
const searchParams = new URLSearchParams();
Object.entries(params).forEach(([key, values]) => {
if (Array.isArray(values)) {
values.forEach((value) => {
searchParams.append(key, value);
});
} else {
searchParams.append(key, values);
}
});
return searchParams;
}
console.log(createSearchParams({
foo: ["bar", "baz"],
hello: "world",
}).toString());
This provides an abstraction, so if you decide you'd rather use the init approach under the hood, you can do so, e.g.:
function createSearchParams(params) {
return new URLSearchParams(Object.entries(params).flatMap(([key, values]) => Array.isArray(values) ? values.map((value) => [key, value]) : [[key, values]]));
}
console.log(createSearchParams({
foo: ["bar", "baz"],
hello: "world",
}).toString());
All of your tests will continue to pass, you don't have to change the consuming code. This is much neater than defining a function to convert the structure you want to the thing URLSearchParams needs, as shown in RenaudC5's answer.
console.log('' + new URLSearchParams(['bar', 'baz', 'qux'].map(v=>['foo', v])))
I am trying to use the value from an object. I don't need to use the 'key' part of the object. Also, I need to remove the curly brackets { } from around the object. How can I do so ?
I'm making a searchbar that sends data into backend, but I only need to use the 'value' part of the object. Perhaps I should use query strings for that ?
const handleClick = (e) => {
e.preventDefault() // stop page from refresh
if (setUserInput !== '') { // is searchbar isnt empty, then run the POST method to send
// data from {userInput} which is the searchbar
const options =
{
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({userInput}),
};
const response = fetch('http://localhost:3001/search', options);
console.log("search entered");
};
}
in the backend when I console.log the response it gives me
`"{ userInput: 'lollol' }"
and out of all of that I only need the 'lollol' part ( lollol is just an example, but whatever is typed into the searchbar will be shown instead of it ). As such:
const searchResult = req.body /// return lollol ✅
not
const searchResult = req.body /// return `"{ userInput: 'lollol' }" ❌
Should I use query strings to accomplish this, or is there a way to extract data from an object?
You don't need to stringify the object in the body. Replace
body: JSON.stringify({userInput}),
with
body: {userInput:userInput},
And then you can use it like req.body.userInput.
Hope, it helps!!
I'm using Fetch (Fetch API) in a project and I would like to, for consistence purposes, create a function that receives all the parameters such as method, url and data and creates the correct request, depending if it's a GET or a POST request.
Is it possible, using Fetch, to send a data object that for the GET request, converts data into and string with the parameters and if it is a POST request, it just sends the data object in the body?
It would look like this:
fetch ('/test', {
method: 'GET',
data: {
test: 'test'
}
});
This doubt was inspired by this jQuery ajax behaviour:
$.ajax({
url: '/test',
method: 'GET',
data: {
test: 'test'
}
});
This would produce this request:
'/test/?test=test'
If I pass the data object as normal in the fetch constructor for a
GET request, would it send the request like the example I gave
'/test/?test=test'
If you want to add query string to a fetch request :
From the SPEC
var url = new URL("https://a.com/method"),
params = {a:1, b:2}
Object.keys(params).forEach(key => url.searchParams.append(key, params[key]))
fetch(url)
this will produce a request :
you could either use the Url class:
var url = new URL("/test/")
Object.keys({test: 'test', a: 1}).forEach(key => url.searchParams.append(key, params[key]))
fetch(url);
or parse the string yourself, if you want wider browser support:
var params = {test: 'test', a: 1},
qs = Object.keys(params).reduce(function(_qs, k, i){ return _qs + '&' + k + '=' + params[k]; }, '').substring(1);
console.log(qs)
I'll show you snippets for creating query with and without using URLSearchParams.
The code will be in typescript for the if you're using it. if not, just remove types, it will work in the same way.
Simple solution (URLSearchParams)
/**
* Creates query from given object
* - It doesn't work with deep nesting
* - It doesn't remove empty fields
* #returns `state1=6&state2=horse` without `?`
*/
function createQuery(queryObject?: Record<string | number, unknown> | null): string {
if (queryObject == null) return ""
// Typescript: The `as ...` expression here is ok because `URLSearchParams` will convert non-string by itself
const searchParams = new URLSearchParams(queryObject as Record<string, string>)
return searchParams.toString()
}
Solving problems solution (URLSearchParams)
/**
* Creates query from given object
* - It doesn't work with deep nesting
* - Removes empty fields
* #returns `state1=6&state2=horse` without `?`
*/
function createQuery(queryObject?: Record<string | number, unknown> | null): string {
if (queryObject == null || !Object.keys(queryObject).length) return ""
for (const key in queryObject) {
if (Object.prototype.hasOwnProperty.call(queryObject, key)) {
const value = queryObject[key]
// Use `!value` expression if you want to delete values as `0` (zero) and `""` (empty string) too.
if (value == null) delete queryObject[key]
}
}
const searchParams = new URLSearchParams(queryObject as Record<string, string>)
return searchParams.toString()
}
No URLSearchParams solution
/**
* Creates query from given object
* - Supports prefixes
* - Supports deep nesting
* - Removes empty fields
* #returns `state1=6&state2=horse` without `?`
*/
function createQuery(queryObject?: Record<string | number, unknown> | null, keyPrefix?: string): string {
if (queryObject == null || !Object.keys(queryObject).length) return ""
keyPrefix = keyPrefix ? (keyPrefix + "_") : ""
const queryKeys = Object.keys(queryObject)
const queryArray = queryKeys.map(key => {
const value = queryObject[key]
if (value) {
if (isDictionary(value)) {
return createQuery(value, keyPrefix + key + "_")
}
return keyPrefix + encodeURIComponent(key) + "=" + encodeURIComponent(String(value))
}
return ""
})
return queryArray.filter(Boolean).join("&")
}
isDictionary Helper
I used isDictionary helper here too, you can find it here
Usage
You need to put ? in the beginning of your endpoint plus createQuery
fetch("/test?" + createQuery({ foo: 12, bar: "#user->here", object: { test: "test", bird: { super: { ultra: { mega: { deep: "human" }, shop: 7 } }, multiple: [1, 2, 3] } } }))
Result
foo=12&bar=%40user-%3Ehere&object_test=test&object_bird_super_ultra_mega_deep=human&object_bird_super_ultra_shop=7&object_bird_multiple=1%2C2%2C3
or
foo: 12
bar: #user->here
object_test: test
object_bird_super_ultra_mega_deep: human
object_bird_super_ultra_shop: 7
object_bird_multiple: 1,2,3
Conclusion
We got different snippets you can choose from depending on your goals.
I'm trying to deserialize a paginated end point. The return request for this end point looks like
{
count: number,
next: string,
previous: string,
data: Array[Objects]
}
The issue I'm having when using js-data to do a findAll, it's injecting this object into the data store. It should be injecting the objects in the data array into the store. So I made a deserialize method on my adapter that looks like this.
deserialize: (resourceConfig:any, response:any) => {
let data = response.data;
if (data && 'count' in data && 'next' in data && 'results' in data) {
data = data.results;
data._meta = {
count: response.data.count,
next: response.data.next,
previous: response.data.previous
};
}
return data;
}
And this works. The array objects are getting injected into my data store. But the meta information is getting lost.
dataStore.findAll('User').then(r => console.log(r._meta)); // r._meta == undefined
I would like to keep that meta information on the returned object. Any ideas?
To do this in v3 you just need to override a couple methods to tweak JSData's
handling of the response from your paginated endpoint. The two most important
things are to tell JSData which nested property of the response are the records
and which nested property should be added to the in-memory store (should be the
same nested property in both cases).
Example:
const store = new DataStore({
addToCache: function (name, data, opts) {
if (name === 'post' && opts.op === 'afterFindAll') {
// Make sure the paginated post records get added to the store (and
// not the whole page object).
return DataStore.prototype.addToCache.call(this, name, data.results, opts);
}
// Otherwise do default behavior
return DataStore.prototype.addToCache.call(this, name, data, opts);
}
});
store.registerAdapter('http', httpAdapter, { 'default': true });
store.defineMapper('post', {
// GET /posts doesn't return data as JSData expects, so we've got to tell
// JSData where the records are in the response.
wrap: function (data, opts) {
// Override behavior of wrap in this instance
if (opts.op === 'afterFindAll') {
// In this example, the Post records are nested under a "results"
// property of the response data. This is a paginated endpoint, so the
// response data might also have properties like "page", "count",
// "hasMore", etc.
data.results = store.getMapper('post').createRecord(data.results);
return data
}
// Otherwise do default behavior
return Mapper.prototype.wrap.call(this, data, opts);
}
});
// Example query, depends on what your backend expects
const query = { status: 'published', page: 1 };
posts.findAll(query)
.then((response) => {
console.log(response.results); // [{...}, {...}, ...]
console.log(response.page); // 1
console.log(response.count); // 10
console.log(response.hasMore); // true
});
I am making a POST request:
var offers = $resource('/api/offers/:id', {
id: '#id'
}
offers.save({}, { name: $scope.newOfferName }, function (offerId) {
$location.path('/offers/' + offerId);
});
I expect offerId to be a string "key", but instead I get an array [0] "k", [1] "e", [2] "y".
On the backend I use Nancy and I return the response using:
Post["/"] = _ =>
{
return Response.AsText("key");
};
The response Header say Content-Type:text/plain and in Chrome preview (Network tab) I can see "key".
When I return an object as JSON it's working fine, but I don't want to create fake class (having a string field) just to pass a string to the client.
I assume Nancy is fine here. What is happening with Angular?
You don't have to create a class for that. You can use an anonymous type:
Post["/"] = _ =>
{
return Response.AsJson(new { offerId = "key" });
};
angular has a default transform that tries to parse incoming data as json.
https://github.com/angular/angular.js/blob/master/src/ng/http.js#L94
You could remove that transform from the transformers array in $http completely, or replace it with one that checks the content-type before trying to transform the data.