Raj
Raj

Reputation: 41

How to take the values from the case sensitive query param variables?

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?

Upvotes: 4

Views: 7067

Answers (3)

Brad C
Brad C

Reputation: 2982

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([]);
  });
});

Upvotes: 2

Oscar Paz
Oscar Paz

Reputation: 18292

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.

Upvotes: 11

vogomatix
vogomatix

Reputation: 5041

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

Upvotes: 0

Related Questions