3
votes

Question is about how to use the object type for sending headers, other than HttpHeaders provided in the HTTPClient declaration.

When using Angular HttpClient, I want to pass the headers with Object, I couldn't understand how to define an object of type [header: string]: string | string[]; Please help me to understand this object declaration. Same i am facing for HttpParams as well. My code approach as below.

getLoggedInUser(requestHeaderParam: GetLoggedInUserHeaderRequestParam): Observable<LoggedInUserResponse> {
   return this.http.get<LoggedInUserResponse>(`${environment.apiBaseUrl}/auth/loggedInUser`,
    { headers: requestHeaderParam }); 
}

The error message i am getting in VS code as below

[ts] Argument of type '{ headers: GetLoggedInUserHeaderRequestParam; }' is not assignable to parameter of type '{ headers?: HttpHeaders | { [header: string]: string | string[]; }; observe?: "body"; params?: Ht...'. Types of property 'headers' are incompatible. Type 'GetLoggedInUserHeaderRequestParam' is not assignable to type 'HttpHeaders | { [header: string]: string | string[]; }'. Type 'GetLoggedInUserHeaderRequestParam' is not assignable to type '{ [header: string]: string | string[]; }'. Index signature is missing in type 'GetLoggedInUserHeaderRequestParam'.

The Request Param type as below

export interface GetLoggedInUserHeaderRequestParam {
  uid: string;
  PSID?: string;
}

The HttpClient Declaration as below.

HttpClient.get(url: string, options: {
   headers?: HttpHeaders | {
    [header: string]: string | string[];
   };
   observe?: "body";
  params?: HttpParams | {
    [param: string]: string | string[];
  };
  reportProgress?: boolean;
  responseType: "arraybuffer";
  withCredentials?: boolean;
}): Observable<ArrayBuffer>

Please help!!!

Note: My Question is not how to use HttpHeaders, I get how to use the HttpHeaders, My question is how to use the Object directly as mentioned in one of its declaration type { [header: string]: string | string[]; } in the HttpClient

3

3 Answers

3
votes

Problem is difference in signature of GetLoggedInUserHeaderRequestParam and Headers direct object

for example

  export interface GetLoggedInUserHeaderRequestParam {
      uid: string;
      PSID?: string;
    }

only accept object with uid and optional PSID wherease Headers direct object

   {
      [header: string]: string | string[];
   }

Says it can accept object with any number of keys of type string and value as string or array of string.

key difference is your interface force Typescript to accept object with exact key name where header object can accept any number of key with type string like

{
  "uid" : "1234",
  "PSID" : "1223",
  "name" : "test",
  ..... 
}

you can solve problem by define interface as

interface GetLoggedInUserHeaderRequestParam {
    [name: string] : string | string[];

}

and call HTTP method as

let header: GetLoggedInUserHeaderRequestParam  = {
  "uid" : "1234",
  "PSID" : "1234"
}

getLoggedInUser(header);
2
votes

it should be something like this:

headers is HTTP headers. what you provide is data

getLoggedInUser(requestHeaderParam: GetLoggedInUserHeaderRequestParam): Observable<LoggedInUserResponse> {

     let headers = new HttpHeaders();
     headers = headers.set('Content-Type', 'application/json');

   return this.http.get<LoggedInUserResponse>(`${environment.apiBaseUrl}/auth/loggedInUser`, JSON.stringify(requestHeaderParam),
    { headers: headers }); 
}

for params:

    import {HttpParams} from "@angular/common/http";

getLoggedInUser(requestHeaderParam: GetLoggedInUserHeaderRequestParam): Observable<LoggedInUserResponse> {

    const params = new HttpParams()
        .set('uid', requestHeaderParam.uid)
        .set('PSID', requestHeaderParam.PSID);

     return this.http.get<LoggedInUserResponse>(`${environment.apiBaseUrl}/auth/loggedInUser`, 
        {params:params}); 
}
2
votes

You're trying to pass an object conforming to GetLoggedInUserHeaderRequestParam as headers or params to HttpClient.get's options but that's not type safe and that's why TypeScript is stopping you from doing it.

See, you declare the parameter requestHeaderParam: GetLoggedInUserHeaderRequestParam. This says that requestHeaderParam:

  1. must have a uid field that is a string.
  2. may have PSID field that is a string.

But it does not say anything about any additional fields. An object could satisfy the interface and contain other fields. And some of these other fields may not be string. Here's an illustration. I took parts of your code and removed some things that are not important for the sake of illustrating what I'm talking about:

interface GetLoggedInUserHeaderRequestParam {
    uid: string;
    PSID?: string;
}

function getLoggedInUser(requestHeaderParam: GetLoggedInUserHeaderRequestParam): void {
}

const params = {
    uid: "1",
    rogue: getLoggedInUser, // Let's pass a function!
};

// This compiles just fine despite params having an extra field.
getLoggedInUser(params);

// This does not compile.
getLoggedInUser({
    uid: "1",
    rogue: getLoggedInUser,  // Let's pass a function!
})

I've put two examples of calls to getLoggedInUser the first compiles fine. The second does not. The 2nd example might lead some users of TypeScript to think that if a field is not defined on an interface that field is disallowed but this is not generally the case. When TypeScript applies an interface to an object literal then it rejects those fields not defined in the interface, but this behavior applies only to object literals. (And it can be overriden with a type assertion.) The first call to getLoggedInUser I show above shows you what happens in the general case: an object can satisfy an interface and yet have additional fields.

So why is it a problem? The { [header: string]: string | string[] } declaration for headers tells you that HttpClient.get wants an object whose keys are strings and whose values are either strings or arrays of string. The values cannot be of any other type. As we've just seen, there is no guarantee that requestHeaderParam will not have fields that violate this requirement. It will have a uid field which is a string, it may have a PSID field which, if present, is a string, but it may also have other fields that are not strings or arrays of strings. So the compiler rightfully raises an error. The same logic applies if you try to use params.

What solution is called for depends mostly on the context in which your code is being used. A minimal solution would be to simply do a type assertion:

this.http.get(..., { headers: requestHeaderParam as unknown as Record<string, string>})

You have to transit through unknown (or any) first because the types are wholly incompatible. Note that if you just use a type assertion alone there's no protection if a value which is not string or string[] is passed. The data will just be passed on to HttpClient.get and you'll get an error or undefined behavior.

Or you could do the type assertion after testing that the object does not have any additional fields.

Or it may make more sense to make requestHeaderParam an object of a specific class which a) enforces which fields may be set, and b) has a method that returns a plain JS object which conforms to what headers requires.