Reputation: 9457
I am using latest version of angular (8). I am doing a conversion of my Http request from the original http to the new http client. I am calling a GET API where I am sending a nested params in the following way:
let data: any = {filters: {"4e9bc554-db54-4413-a718-b89ffdb91c2f": "465c1ab-2b89-4b51-8a7b-5d2ac862ee32"}, is_indexed: true}
return this.httpClient.get<Register[]>('/registers/', {headers: headers, params: data});
The above code, the nested params are not recognised. How do I properly pass as nested params using the HttpClient? Ps In the old Http, there was no issue using the above code. Thanks for the help in advance.
UPDATE Url would look similar to this:
Upvotes: 6
Views: 4579
Reputation: 391
After many searching, I think that there is no way to pass nested json object to get api. Nethertheless, I have a solution for when your webservice is build with spring boot. Here is what you can do.
At the angular side I sugest you to encode your json query to base64 to shorten it:
let data: any = {filters: {"4e9bc554-db54-4413-a718-b89ffdb91c2f": "465c1ab-2b89-4b51-8a7b-5d2ac862ee32"}, is_indexed: true}
let objJsonStr = JSON.stringify(data);
let objJsonB64 = btoa(objJsonStr);
let queryParams = new HttpParams();
queryParams = queryParams.append('q', objJsonB64);
return this.httpClient.get<Register[]>('/registers/', {headers: headers, params: queryParams});
At the spring boot side: Create a servletRequest mapper that will help to transform you query and inject in the body
package com.example.ws.servlet;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Base64;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
public class SeachQueryHttpServletRequestWrapper extends HttpServletRequestWrapper {
private static final String CHARSET = "UTF-8";
private static final String CONTENT_TYPE = "application/json";
private boolean loaded;
public SeachQueryHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
}
@Override
public ServletInputStream getInputStream() throws IOException {
if (loaded) {
throw new RuntimeException("Already loaded!");
}
var query = getRequest().getParameter("q");
var decodedBytes = Base64.getDecoder().decode(query);
var decodedString = new String(decodedBytes);
try (var body = new BodyInputStream(decodedString.getBytes(getCharacterEncoding()))) {
loaded = true;
return body;
} catch (Exception e) {
throw new RuntimeException();
}
}
@Override
public String getCharacterEncoding() {
return CHARSET;
}
@Override
public String getContentType() {
return CONTENT_TYPE;
}
/**
* The exact copy of
* @see org.springframework.web.servlet.function.DefaultServerRequestBuilder$BodyInputStream
*/
private static class BodyInputStream extends ServletInputStream {
private final InputStream delegate;
public BodyInputStream(byte[] body) {
this.delegate = new ByteArrayInputStream(body);
}
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return true;
}
@Override
public void setReadListener(ReadListener readListener) {
throw new UnsupportedOperationException();
}
@Override
public int read() throws IOException {
return this.delegate.read();
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
return this.delegate.read(b, off, len);
}
@Override
public int read(byte[] b) throws IOException {
return this.delegate.read(b);
}
@Override
public long skip(long n) throws IOException {
return this.delegate.skip(n);
}
@Override
public int available() throws IOException {
return this.delegate.available();
}
@Override
public void close() throws IOException {
this.delegate.close();
}
@Override
public synchronized void mark(int readlimit) {
this.delegate.mark(readlimit);
}
@Override
public synchronized void reset() throws IOException {
this.delegate.reset();
}
@Override
public boolean markSupported() {
return this.delegate.markSupported();
}
}
}
Add a filter to transform your request
package com.example.ws.filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import org.springframework.context.annotation.Configuration;
import com.example.ws.servlet.SeachQueryHttpServletRequestWrapper;
@Configuration
public class SeachQueryFilter implements Filter {
@Override
public void doFilter(ServletRequest request,
ServletResponse response, FilterChain filterchain)
throws IOException, ServletException {
var query = request.getParameter("q");
if( query != null ) {
var wrapper = new SeachQueryHttpServletRequestWrapper((HttpServletRequest) request);
filterchain.doFilter(wrapper, response);
}else {
filterchain.doFilter(request, response);
}
}
}
And voilà!
Upvotes: 1
Reputation: 71
You can use qs to stringify your object to url query string and then use the native fromString import on HttpParams constructor
let data: any = {filters: {"4e9bc554-db54-4413-a718-b89ffdb91c2f": "465c1ab-2b89-4b51-8a7b-5d2ac862ee32"}, is_indexed: true}
const params = new HttpParams({fromString: qs.stringify(data)});
return this.httpClient.get<Register[]>('/registers/', {headers: headers, params});
Work fine for me with complex object.
See https://npmjs.com/package/qs and https://www.npmjs.com/package/@types/qs
Upvotes: 1
Reputation: 1291
Here's my working method of passing nested objects in Angular by serializing with JSON.stringify
:
const stringifiedParams = JSON.stringify(this.filterParams);
return this.http.get<any[]>(`jobs`, {params: {filter: stringifiedParams}});
In Node this deserializes the params with JSON.parse
:
filter = JSON.parse(req.query.filter.toString());
Upvotes: 1
Reputation: 1980
You could recursively iterate through the data object to flatten it out.
getData(data: any) {
let params = {};
this.buildHttpParams(params, data, "");
return this.httpClient.get("...", {params: params})
}
private buildHttpParams(params: any, data: any, currentPath: string) {
Object.keys(data).forEach(key => {
if (data[key] instanceof Object) {
this.buildHttpParams(params, data[key], `${currentPath}${key}.`);
} else {
params[`${currentPath}${key}`] = data[key];
}
});
}
This will change our data object from:
{
filters: {
"4e9bc554-db54-4413-a718-b89ffdb91c2f": "465c1ab-2b89-4b51-8a7b-5d2ac862ee32"
},
"is_indexed": true
}
to:
{
"filters.4e9bc554-db54-4413-a718-b89ffdb91c2f": "465c1ab-2b89-4b51-8a7b-5d2ac862ee32",
"is_indexed": true
}
Upvotes: 4
Reputation: 789
Your solution should be implemented like this now:
import { HttpClient,HttpParams } from '@angular/common/http';
let params: any = new HttpParams();
// Begin assigning parameters
params = params.append('filters', {"4e9bc554-db54": "23473473-7938", "555555-db54": "33333-7938"});
params = params.append('is_indexed', true);
return this.httpClient.get<Register[]>('/registers/', {headers: headers, { params });
Other than this there's a set property too, you can check further in the Angular's Documentation.
Upvotes: -1