4

I have a URL with the query string id. But, the variable id can be come as 'id' or 'Id' in the URL.

As per my understanding, these 2 will be treated differently. For handling the following URL's, I have wrote the code as following:

http://xxx/abc?id=10

http://xxx/abc?Id=10

private findQueryParams() {
  this._router.queryParams.subscribe((params: Params) => {
    if (Object.keys(params).length) {
      let id = 0;
      if (params['id']) {
        id = Number(params['id']);
      } else if (params['Id']) {
        id = Number(params['Id']);
      }
    }
  });
}

Other than this, is there any simple way to handle the case sensitive variables and take the values properly in angular4?

7
  • The simple answer is that you should not be writing a site which has id and Id as url parameters. Commented Apr 30, 2018 at 10:03
  • @vogomatix I wrote my site with query param as 'id' only. But, my concern was, if somebody tries to access it with 'Id' or 'iD', it should still work. Or are you trying to say that if somebody tries to access with case sensitive params, it shoudn't work? Commented Apr 30, 2018 at 10:43
  • what about params[0]? Commented Apr 30, 2018 at 10:56
  • @Eliseo No, I think it is not a good option. Commented Apr 30, 2018 at 11:01
  • @Raj There is no need to make it work in a case insensitive fashion. If the parameter is not in the case you expect the site should not accept it Commented Apr 30, 2018 at 14:56

3 Answers 3

11

As @vogomatix said, you shouldn't have the same param with different names. But, if for whichever reason you do, you can simplfy your code a bit like this:

private findQueryParams() {
    this._route.queryParams.subscribe((params: Params) => {
        const idValue = params['id'] || params['Id'] || '0';
        let id = Number(idValue);
    });
}

This way you'll use the value of id, if not exists, then Id, and if doesn't exists, either, then you'll use '0'

If there can be more combinations, as you say, then your best option is to clone the params to a lowercase version:

function toLower(params: Params): Params {
    const lowerParams: Params = {};
    for (const key in params) {
        lowerParams[key.toLowerCase()] = params[key];
    }

    return lowerParams;
}

With this:

private findQueryParams() {
    this._route.queryParams.subscribe((params: Params) => {
        const _params = toLower(params);
        const idValue = params['id'] || '0';
        let id = Number(params);
    });
}

Of course you've got to this every time you get params from the Router, but that is unavoidable, as the base defect is in the Url system.

Sign up to request clarification or add additional context in comments.

2 Comments

Sorry, this is not what I expected.This is almost the same as what I've done. In the example you have give, if the url comes with 'iD', it will not work, right? Or you are saying to add one more similar condition in the code? With out doing these condition checks, is there any simple way like converting the entire url to lowecase or something like that?
Check my extended answer. I'm afraid that's the best you can get
2

After perusing the internet and not finding a satisfactory solution, I wrote my own re-usable class that wraps the built-in Params object and provides case-insensitive access to query string parameters.

You can use it in your component like:

import { ActivatedRoute } from '@angular/router';
import { CaseInsensitiveParamMap } from '../case-insensitive-param-map';

constructor(private activatedRoute: ActivatedRoute) {}

ngOnInit(): void {
  // subscribe to query params changes:
  this.activatedRoute.queryParams.snapshot.paramMap
    .subscribe((params) => this.handleQueryParamsChanged(new CaseInsensitiveParamMap(params)));

  // or use the params from the route snapshot:
  const paramMap = new CaseInsensitiveParamMap(this.activatedRoute.snapshot.queryParams);
  const returnUrl = paramMap.get('returnUrl');
}

private handleQueryParamsChanged(paramMap: CaseInsensitiveParamMap): void {
  const returnUrl = paramMap.get('returnUrl');
  // Returns the value of the first query param named 'returnUrl' case-insensitively
  // See the test suite below for more examples
}

Note that I also added a getNumber helper method that makes it much easier to parse numeric query params. It returns null if the value is not a number or is not present.

Here is the class:

case-insensitive-param-map.ts

import { ParamMap, Params } from '@angular/router';

export class CaseInsensitiveParamMap implements ParamMap {
  private params: Params;

  constructor(params: Params) {
    this.params = params || {};
  }

  has(name: string): boolean {
    return Object.keys(this.params).some((key) => key.toLowerCase() === name.toLowerCase());
  }

  private getKeysCaseInsensitively(name: string): string[] {
    return Object.keys(this.params).filter((key) => key.toLowerCase() === name.toLowerCase());
  }

  get(name: string): string | null {
    if (this.has(name)) {
      const keys = this.getKeysCaseInsensitively(name);
      const v = this.params[keys[0]];
      return Array.isArray(v) ? v[0] : v;
    }

    return null;
  }

  getNumber(name: string): number | null {
    const v = this.get(name);
    return !v || isNaN(Number(v)) ? null : Number(v);
  }

  getAll(name: string): string[] {
    if (this.has(name)) {
      const result: string[] = [];
      this.getKeysCaseInsensitively(name).forEach((key) => {
        const v = this.params[key];
        result.push(...(Array.isArray(v) ? v : [v]));
      });
      return result;
    }

    return [];
  }

  get keys(): string[] {
    return Object.keys(this.params);
  }
}

Here is the tests that outline the behavior and serve as documentation:

case-insensitive-param-map.spec.ts

import { Params } from '@angular/router';
import { CaseInsensitiveParamMap } from './case-insensitive-param-map';

describe('CaseInsensitiveParamMap', () => {
  it('should return whether a case-insensitive parameter is present', () => {
    const params: Params = { single: 's', multiple: ['m1', 'm2'] };
    const map: CaseInsensitiveParamMap = new CaseInsensitiveParamMap(params);
    expect(map.has('single')).toEqual(true);
    expect(map.has('SINGLE')).toEqual(true);
    expect(map.has('multiple')).toEqual(true);
    expect(map.has('MULTIPLE')).toEqual(true);
    expect(map.has('not here')).toEqual(false);
  });

  it('should return the keys of the parameters', () => {
    const params: Params = { single: 's', multiple: ['m1', 'm2'] };
    const map: CaseInsensitiveParamMap = new CaseInsensitiveParamMap(params);
    expect(map.keys).toEqual(['single', 'multiple']);
  });

  it('should support single valued parameters', () => {
    const params: Params = { single: 's', multiple: ['m1', 'm2'] };
    const map: CaseInsensitiveParamMap = new CaseInsensitiveParamMap(params);
    expect(map.get('single')).toEqual('s');
    expect(map.get('multiple')).toEqual('m1');
  });

  it('should get numeric query string values as numbers', () => {
    const params: Params = { single: '1', multiple: ['2', '3'], nonnumeric: 'foo', empty: '', zero: '0', nullProp: null, undefinedProp: undefined };
    const map: CaseInsensitiveParamMap = new CaseInsensitiveParamMap(params);
    expect(map.getNumber('single')).toEqual(1);
    expect(map.getNumber('multiple')).toEqual(2);
    expect(map.getNumber('zero')).toEqual(0);
    expect(map.getNumber('nullProp')).toEqual(null);
    expect(map.getNumber('undefinedProp')).toEqual(null);
    expect(map.getNumber('nonnumeric')).toEqual(null);
    expect(map.getNumber('not here')).toEqual(null);
    expect(map.getNumber('empty')).toEqual(null);
  });

  it('should support case-insensitive single valued parameters', () => {
    const params: Params = { SINGLE: 'S', MULTIPLE: ['M1', 'M2'] };
    const map: CaseInsensitiveParamMap = new CaseInsensitiveParamMap(params);
    expect(map.get('single')).toEqual('S');
    expect(map.get('SINGLE')).toEqual('S');
    expect(map.get('Single')).toEqual('S');
    expect(map.get('multiple')).toEqual('M1');
    expect(map.get('MULTIPLE')).toEqual('M1');
    expect(map.get('Multiple')).toEqual('M1');
  });

  it('should support multiple valued parameters', () => {
    const params: Params = { single: 's', multiple: ['m1', 'm2'] };
    const map: CaseInsensitiveParamMap = new CaseInsensitiveParamMap(params);
    expect(map.getAll('single')).toEqual(['s']);
    expect(map.getAll('multiple')).toEqual(['m1', 'm2']);
  });

  it('should support case-insensitive multiple valued parameters', () => {
    const params: Params = { SINGLE: 'S', MULTIPLE: ['M1', 'M2'], multiple: ['M3'] };
    const map: CaseInsensitiveParamMap = new CaseInsensitiveParamMap(params);
    expect(map.getAll('single')).toEqual(['S']);
    expect(map.getAll('multiple')).toEqual(['M1', 'M2', 'M3']);
  });

  it('should return null when a single valued element is absent', () => {
    expect(new CaseInsensitiveParamMap({}).get('not here')).toEqual(null);
  });

  it('should return [] when a multiple valued element is absent', () => {
    expect(new CaseInsensitiveParamMap({}).getAll('not here')).toEqual([]);
  });
});

3 Comments

One technical note though: It does not use toLocaleLowerCase or the Angular equivalent, so the lower-case comparison is not localized. (Because lower case means different things in different languages...)
Also asked Angular whether they might include it in Angular directly, as I found it useful.
0

If you wish to have a component which shows a specific abc item, then you should really be having your URLs without query parameters and embracing the concept of Routes instead

i.e. instead of having http://xxx/abc?id=10 to show the abc item with an id of 10 you should just be having a route http://xxx/abc/10

You would then add this route as:

 { path: 'abc/:id', component: abcComponent },

where abcComponent is the component which shows the relevant item

1 Comment

No, it's my legacy url and I cannot change it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.