Angular Http Client - How to Pass Nested Params Object to GET API
Asked Answered
A

5

6

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:

https://www.somepapi.com/registers/?filters=%7B%224e9bc554-db54-4413-a718-b89ffdb91c2f%22:%228465c1ab-2b89-4b51-8a7b-5d2ac862ee32%22%7D&is_indexed=true;

Ambulator answered 14/11, 2019 at 11:40 Comment(5)
have you tried to pass param:data using let params = new HttpParams();Cusp
and one thing pass value as string in data. change this is_indexed:true to is_indexed:"true" JSON.stringify() `could help you though.Cusp
What is the structure of the query string you want? Could you include the sample of the desired url.Granulose
This documentation can help you further tektutorialshub.com/angular/…Mcgriff
Example url (not same but similar to example ) api.example.com/registers/…Ambulator
P
4

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
}
Pylos answered 11/5, 2020 at 8:7 Comment(0)
E
1

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());
Elonore answered 10/3, 2021 at 23:49 Comment(0)
L
1

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

Leupold answered 14/1, 2022 at 17:12 Comment(0)
P
1

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à!

Prophylaxis answered 15/8, 2023 at 1:54 Comment(0)
M
-1

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.

Mcgriff answered 14/11, 2019 at 12:27 Comment(2)
yeah just tested it doesn't work. 'filters' expects a string a value and when I stringify it, the API doesn't recognise it as a nested object but string valueAmbulator
I'll check this in few hours and get back to youMcgriff

© 2022 - 2024 — McMap. All rights reserved.