question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Support for object in queryParams

See original GitHub issue

Which @angular/* package(s) are relevant/related to the feature request?

router

Description

When we create an url tree, the “tree” function will serialize the queryParams for us.

While this serialization is mandatory for the object to fit into an url, that one is in my understanding not done in the right place. The UrlSerializer is supposed to do that job, but it cannot do it properly anymore because the queryParams has already been serialized. For instance, if you want to pass an object into a queryParam, you will end up having a serialized string: [object Object]. There is another ticket suggesting allowing overriding this serializing.

The typing of queryParams is also confusing. Here is the type

export declare type Params = {
    [key: string]: any;
};

While accessing the params through ParamMap returns a string

export declare interface ParamMap {
    /**
     * Reports whether the map contains a given parameter.
     * @param name The parameter name.
     * @returns True if the map contains the given parameter, false otherwise.
     */
    has(name: string): boolean;
    /**
     * Retrieves a single value for a parameter.
     * @param name The parameter name.
     * @return The parameter's single value,
     * or the first value if the parameter has multiple values,
     * or `null` when there is no such parameter.
     */
    get(name: string): string | null;
    /**
     * Retrieves multiple values for a parameter.
     * @param name The parameter name.
     * @return An array containing one or more values,
     * or an empty array if there is no such parameter.
     *
     */
    getAll(name: string): string[];
    /** Names of the parameters in the map. */
    readonly keys: string[];
}

While I understand that we have at some point to serialize the queryParams, the current way is not flexible enough for my needs.

Proposed solution

The first solution I do think about is keeping the queryParams as is, i.e. without serializing them. A Tree has Params which are of type Record<string, any> just keep them so that we can retrieve the original value later.

But I can imagine there are other problems because we want to keep a snapshot of the routes, and we therefore need to serialize those parameters at some point (and this is not even related to the URL but the internal snapshot). I can think there are other as well as internal queryParams comparison from one route to another (where strings or array of strings are easy to compare). Keeping both (the original and the stringified version) would also perhaps be an overkill. Note that I did not browse all the code base to see if there are such problems.

The cleanest solution I can think about would be to delegate the job of serializing to a service ‘ParamsSerializer’ so that we can then inject any logic we want.

// bonus to explicitly show those are generically parsable values, but using "Params" would be fine too
type JSONValue =
    | string
    | number
    | boolean
    | { [x: string]: JSONValue }
    | Array<JSONValue>;

class ParamsSerializer {
  serialize(params: Params): string
  parse(stringParams: string): Record<string, JSONValue>
}

The serialize method would be called at that point

And the parse function to reconstruct the Params (there is no equivalent in the code yet). The ParamMap would also be able to return the JSONValue instead (currently string).

export declare interface ParamMap {
...
    get(name: string): JSONValue | null;
    getAll(name: string): JSONValue[];
}

Alternatives considered

What I do really want is to pass any object within the queryParams and being able to retrieve it back later.

We have 3 problems to solve:

  1. The object we pass must be serializable: we need a generic or custom way to convert our object to a JSONValue one.
  2. Once serialized, they should fit the URL : This is the responsibility of the UrlSerializer
  3. We should be able to revive those objects from the URL:

Currently, I’m simply passing queryParams which are of type Record<string, string>; I just manually serialize my objects and then use my custom parser function.

Here is my helper:

import { Injectable } from '@angular/core';
import JsonURL from '@jsonurl/jsonurl';
import { mapValues } from 'lodash';
import { JSONValue } from '@medulla-core/utils/json';

@Injectable({
  providedIn: 'root',
})
export class QueryParamsService {
  static encode(queryParams?: Record<string, JSONValue>): Record<string, string> | undefined | null {
    if (queryParams === undefined) {
      return undefined;
    }

    if (queryParams === null) {
      return null;
    }

    return mapValues(queryParams, (value) => JsonURL.stringify(value, { noEmptyComposite: true }));
  }

  static decode(queryParams?: Record<string, string>): Record<string, JSONValue> {
    if (queryParams === undefined) {
      return undefined;
    }

    if (queryParams === null) {
      return null;
    }

    return mapValues(queryParams, (value) => JsonURL.parse(value, { noEmptyComposite: true }));
  }
}

I’m using jsonurl to keep a valid, consistent and human readable json into the URL.

For instance, here is an example of the queryParams of a userSearch page:

queryParams = {
filter: {name:"", surname:""},
page: {offset: 0, count: 100},
}

If I try passing this object to the queryParams, without my helper I will get something like this:

?filter=%5Bobject%20Object%5D&page=%5Bobject%20Object%5D

Using my helper, the queryParams becomes:

queryParams = {
filter: "(name:'',surname:'')",
page: "(offset:0,count:100)"
}

Which is url friendly and gives me this clean url

?filter=(name:'',surname:'')&page=(offset:0,count:100)

To get back the object, I’m simply sending the parsed queryParams into my helper.

The goal of this ticket is to automate this process without using my helper.

Side note: As queryParams are related to routes, perhaps it be consistent to allow registering an optional parser / serialize to the routes themselves. We could then even register a reviver for the activeroute’s query params.

Issue Analytics

  • State:open
  • Created a year ago
  • Reactions:26
  • Comments:15 (8 by maintainers)

github_iconTop GitHub Comments

2reactions
atscottcommented, Sep 26, 2022

As discussed, I made both changes (UrlTree should not stringify params, Router should always create a UrlTree from a string) and there were only ~15 global test failures in Google. This is a pretty manageable size for making this sort of change.

2reactions
Xamplecommented, Sep 23, 2022

I think we are on the same page, there must be a unique source of truth and the url should hold this responsibility (when one goes on a page, we need to rebuild the page state using the url). Also, while we could possibly set anything to the url (manually or a url tree through the “UrlSerializer.serialize”, the active route’s queryParams should always results from parsing that url to be consistent. Now if your UrlSerializer is not bijective, you might set a number and get a string back (like the DefaultUrlSerializer ). Thankfully, a smarter UrlSerializer would not be hard to implement.

Read more comments on GitHub >

github_iconTop Results From Across the Web

How to get object in queryparams using Typescript?
Here i need to retrieve the object values, but unable to get it. Kindly help me to retrieve the data through query params...
Read more >
Objects in query params - Medium
Objects in query params. There is a debate over how objects should be represented in the “query” part of URIs. Some people prefer...
Read more >
OpenAPI JSON Objects as Query Parameters - Baeldung
OpenAPI 2 doesn't support objects as query parameters ; only primitive values and arrays of primitives are supported.
Read more >
object-in-queryparams - npm
An extended version of URLSearchParams with support for Objects and Arrays.. Latest version: 1.0.2, last published: a year ago.
Read more >
URLSearchParams - Web APIs - MDN Web Docs
Chrome Edge URLSearchParams Full support. Chrome49. Toggle history Full support. Edge... @@iterator Full support. Chrome49. Toggle history Full support. Edge... URLSearchParams() constructor Full support. Chrome49. Toggle...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found