Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Http ResponseType cannot be set #18586

Open
zaiddabaeen opened this issue Aug 8, 2017 · 72 comments
Open

Http ResponseType cannot be set #18586

zaiddabaeen opened this issue Aug 8, 2017 · 72 comments
Labels
area: common/http feature: under consideration Feature request for which voting has completed and the request is now under consideration feature Issue that requests a new feature
Milestone

Comments

@zaiddabaeen
Copy link

I'm submitting a...


[ ] Regression (a behavior that used to work and stopped working in a new release)
[x] Bug report  
[ ] Feature request
[ ] Documentation issue or request
[ ] Support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question

Current behavior

Response type cannot be set for HttpClient methods.

        const options = {headers: headers, params: params, responseType: 'text'};

        return this.http.get(url, options).share();

Would show an error

  Types of property 'responseType' are incompatible.
    Type 'string' is not assignable to type '"json"'.

Expected behavior

It is expected that the response type should be exported like

export type ResponseType = 'arraybuffer' | 'blob' | 'json' | 'text';;

And one would be able to set it using this type. Otherwise the type cannot be changed.

Environment

Angular version: 4.1.1 and still there in 5.0.0-beta.2
as seen here: https://github.com/angular/angular/blob/master/packages/common/http/src/client.ts

@zaiddabaeen
Copy link
Author

A workaround:

const options: {
            headers?: HttpHeaders,
            observe?: 'body',
            params?: HttpParams,
            reportProgress?: boolean,
            responseType: 'text',
            withCredentials?: boolean
        } = {
            headers: headers,
            params: params,
            responseType: 'text'
        };

@vicb vicb added the comp: http label Aug 8, 2017
@alxhub
Copy link
Member

alxhub commented Aug 9, 2017

@zaiddabaeen currently, this is by design. Typescript needs to be able to infer the observe and responseType values statically, in order to choose the correct return type for get(). If you pass in an improperly typed options object, it can't infer the right return type.

Another workaround is:

const options = {headers, params, responseType: 'text' as 'text'};
return this.http.get(url, options).share();

@zaiddabaeen
Copy link
Author

I understand, but I believe that's unintuitive and confusing to the developers. I don't recall I ever casted a string to a 'string' before. Enumerating and using types as suggested would sound to me as a cleaner solution.

@alxhub
Copy link
Member

alxhub commented Aug 17, 2017

@zaiddabaeen the problem is for:

const res = this.http.get(url, options);

What is the type of res? It depends on the values in options - but Typescript has no way to know what those values are if it's not inlined.

In other words:

const res = this.http.get(url, {responseType: 'text'});

is not equivalent to

const options = {responseType: 'text'};
const res = this.http.get(url, options);

In the first one Typescript can infer that the type of res is Observable<string>, in the second one it cannot be determined via type inference. If we added this feature, we would have to return an Observable<any> which would be a poor experience.

I expect most cases where this is desired can be solved with the spread operator:

// Some options we want to control dynamically.
const options = {headers: ..., params: ...};
const res = this.http.get(url, {...options, responseType: 'text'});

This way Typescript can infer the return type based on the signature and the value of responseType, but options can also be passed in without reconstructing the whole object.

@chrillewoodz
Copy link

chrillewoodz commented Sep 6, 2017

So instead we have to do workarounds to get the desired effect? That can't be the way to go about this. I've been screaming at my computer for quite a while over this now, I've got a service which wraps the HttpClient but trying to set responseType doesn't work, the only way that I can get the error to go away is by doing responseType: 'text' as 'json'.. None of the workarounds above works.

@zaiddabaeen
Copy link
Author

@chrillewoodz The default is JSON however. Why are you casting it to json?
My approach works, and I can confirm that it is now run on production.

@chrillewoodz
Copy link

@zaiddabaeen I'm not casting anything to json (as far as I'm aware). This is what I have:

  public get<T>(url: string, params?: {[key: string]: any}, headers?: HttpHeaders): Observable<T> {
    return this.http.get<T>(this.config.host + url, this.getRequestOptions(params, headers));
  }

  private getRequestOptions(params?: any, customHeaders?: HttpHeaders) {

    let defaultHeaders: HttpHeaders = new HttpHeaders();

    defaultHeaders = defaultHeaders.set('Content-Type', 'application/json');

    return {
      headers: customHeaders || defaultHeaders,
      params: params ? this.convertJSONtoParams(params) : null
    };
  }

Attempting to add responseType: 'text' to the return of getRequestOptions is what throws the error.

@zaiddabaeen
Copy link
Author

Use responseType: 'text' as 'text'

@chrillewoodz
Copy link

None of the workarounds above works.

I have already tried all of the above :|

@roddy
Copy link

roddy commented Sep 20, 2017

I also had this problem, but by removing the <T> from the get call I was able to get it to work using responseType: 'text'as 'text'.

Eg this does not work and returns the error:

const options: { responseType: 'text' as 'text', withCredentials: true };
this.httpClient.get<string>(url, options)

But this does work:

const options: { responseType: 'text' as 'text', withCredentials: true };
this.httpClient.get(url, options)

@yusijs
Copy link

yusijs commented Sep 22, 2017

The only way I got it to work without error was using @roddy's example, and that's with inlined options...

This does not work:
image

and neither will:
image

Angular v4.4.3

@reppners
Copy link

reppners commented Sep 27, 2017

The generic MUST not be used when responseType is specified to something other than json because typeof T will then be inferred automatically.

Take a look at how it is defined for the get() method

    /**
     * Construct a GET request which interprets the body as an `ArrayBuffer` and returns it.
     *
     * @return an `Observable` of the body as an `ArrayBuffer`.
     */
    get(url: string, options: {
        headers?: HttpHeaders;
        observe?: 'body';
        params?: HttpParams;
        reportProgress?: boolean;
        responseType: 'arraybuffer';
        withCredentials?: boolean;
    }): Observable<ArrayBuffer>;
    /**
     * Construct a GET request which interprets the body as a `Blob` and returns it.
     *
     * @return an `Observable` of the body as a `Blob`.
     */
    get(url: string, options: {
        headers?: HttpHeaders;
        observe?: 'body';
        params?: HttpParams;
        reportProgress?: boolean;
        responseType: 'blob';
        withCredentials?: boolean;
    }): Observable<Blob>;
    /**
     * Construct a GET request which interprets the body as text and returns it.
     *
     * @return an `Observable` of the body as a `string`.
     */
    get(url: string, options: {
        headers?: HttpHeaders;
        observe?: 'body';
        params?: HttpParams;
        reportProgress?: boolean;
        responseType: 'text';
        withCredentials?: boolean;
    }): Observable<string>;
    /**
     * Construct a GET request which interprets the body as JSON and returns it.
     *
     * @return an `Observable` of the body as an `Object`.
     */
    get(url: string, options?: {
        headers?: HttpHeaders;
        observe?: 'body';
        params?: HttpParams;
        reportProgress?: boolean;
        responseType?: 'json';
        withCredentials?: boolean;
    }): Observable<Object>;
    /**
     * Construct a GET request which interprets the body as JSON and returns it.
     *
     * @return an `Observable` of the body as type `T`.
     */
    get<T>(url: string, options?: {
        headers?: HttpHeaders;
        observe?: 'body';
        params?: HttpParams;
        reportProgress?: boolean;
        responseType?: 'json';
        withCredentials?: boolean;
    }): Observable<T>;

This makes perfect sense since the type can only be something angular does not know statically when the responseType is json - all other cases there is no need for a generic.

@a-kolybelnikov
Copy link

@reppners Hi Stefan, thank you for the explanation above. However, no matter how I try I cannot make the new HttpClient to work in a service that I am using to intercept image urls in my app:

get(url: string): Observable<any> {
    return new Observable((observer: Subscriber<any>) => {
        let objectUrl: string = null;
          this.http
            .get(url, {headers: this.getHeaders(), responseType: ResponseContentType.Blob} )
            .subscribe(m => {
              objectUrl = URL.createObjectURL(m.blob());
              observer.next(objectUrl);
            });

        return () => {
          if (objectUrl) {
            URL.revokeObjectURL(objectUrl);
            objectUrl = null;
          }
        }
    });
  }
  getHeaders(): Headers {
    let headers = new Headers();

    let token = this.authService.getToken();
    if (token) {
      headers.set('Authorization', 'Bearer ' + token);
    }
    
    return headers;
  }

Neither responseType: 'blob' set directly in the GET request, nor setting it in the options: { } as mentioned above works.

@btory
Copy link

btory commented Oct 13, 2017

@a-kolybelnikov You can use something like this in your case:

.get(url, { headers: this.getHeaders(), responseType: ResponseContentType.Blob as 'blob' })

@akolybelnikov
Copy link

@btory thank you. I have tried and it won’t work.

@rds-rafael
Copy link

try this: .. , responseType: 'text' as 'json'

@robert-king
Copy link

thanks @rds-rafael , this.http.get<ArrayBuffer>(this.pdfLink, {responseType: 'arraybuffer' as 'json'}) worked for me, a bit strange.

@alxhub
Copy link
Member

alxhub commented Nov 28, 2017

Closing, the solution is to pass a correctly typed argument.

@alxhub alxhub closed this as completed Nov 28, 2017
@MathiasCiarlo
Copy link

MathiasCiarlo commented Dec 6, 2017

For blobs:
.get(url, { 'responseType: 'blob' as 'json' })

@claygifford
Copy link

So whoever won the argument to re-factor Http to HttpClient this way is just wrong.

@nortain
Copy link

nortain commented Dec 19, 2017

@chrillewoodz's solution worked for me
I had something like
this.httpClient.post<string>(url, data, {responseType: 'text' as 'json'});
and this worked as the api I was called was simply returning a string. Of course, removing the generic means that the whole thing needed to be changed to:
this.httpClient.post(url, data, {responseType: 'text' as 'text'});

While I find the workaround to this to only be a minor annoyance it was really furustring that it was so difficult to actually find this answer. It'd be nice if this was better documented or I suppose a more intuitive solution be reached.

@alxhub
Copy link
Member

alxhub commented Dec 19, 2017

@nortain you definitely don't need either.

this.httpClient.post(url, data, {responsesType: 'text'})

will give you an Observable<string> without you having to specify <string>. You don't need to say 'text' as 'text', Typescript knows that.

The only time you should be passing a type parameter to an HttpClient method is with responseType: 'json'. All of the others are implicit.

@nortain
Copy link

nortain commented Dec 19, 2017

@alxhub Ah thank you, that makes more sense and I realize i hadn't tried dropping the as 'text' when removing the generic. Also knowing that the generic is intended for json responses only helps. I appreciate the follow up.

@uzumakinaruto123
Copy link

@alxhub
Facing the same issue with observe property

this.http.get(endpoint, {
      observe: 'response'
    })
[ts]
Argument of type '{ headers: HttpHeaders; observe: string; }' is not assignable to parameter of type '{ headers?: HttpHeaders | { [header: string]: string | string[]; }; observe?: "body"; params?: Ht...'.
  Types of property 'observe' are incompatible.
    Type 'string' is not assignable to type '"body"'.

only observe: 'body' works. 'response' and 'events' does not.

@Pastafarian
Copy link

All feels very convoluted to me. I'd rather have different methods to call for different results rather than the inference which is currently being used.

@inthegarage
Copy link

@Pastafarian I agree, in one case I see (with the same responseType option) that:

return this.httpClient.request('post', '/login', options)

Does work, this on the other hand does not:

return this.httpClient.post('/login', options)

Not sure I agree this httpclient is any easier or more intuitive than the previous incarnation. On the contrary it's clunky and relies on a lot of under the covers inference.

@tobihagemann
Copy link

I can't really follow. Just stumbled upon this in my code:

login(username: string, password: string): Observable<string> {
  const body = `username=${username}&password=${password}`;
  const options = {
    headers: new HttpHeaders({ 'Content-Type': 'application/x-www-form-urlencoded' }),
    responseType: 'text'
  };
  return this.http.post(this.loginUrl, body, options);
}

Using the latest version of VS Code, it tells me in the last line that options is wrong:

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

Reading the comments, I have the feeling that I have to write responseType: 'text' as 'text' or responseType: 'text' as 'json' as a workaround. I don't get it, what does that even mean?

@alxhub
Copy link
Member

alxhub commented Jan 11, 2018

@tobihagemann responseType: 'text' as 'text' is what you want. 'text' is not 'json'.

@wunaidage
Copy link

wunaidage commented Mar 11, 2019

I have to hack it with
const options = {responseType: 'text' as 'json'};
this.httpClient.get<string>(url, options).subscribe()

@OscarDev
Copy link

Cast the httpOptions variable to type Object:

const httpOptions: Object = {
  responseType: 'blob'
};

as specified in the docs:https://angular.io/api/common/http/HttpClient#get

@kippllo
Copy link

kippllo commented Jun 14, 2019

Any news on this? I've been struggling with the same issue on HttpClient.post's options...

@MartinX3
Copy link

Working with Angular 7 (8 not tested yet)

import { HttpClient, HttpHeaders, HttpResponse } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';

@Injectable()
export class RequestService {
  httpOptions = {
    headers: new HttpHeaders({
      Accept: 'application/json;charset=utf-8',
      Authorization: `Basic ${btoa('user:password')}`,
      'Content-Type': 'application/json;charset=utf-8'
    }),
    observe: 'response' as 'body'
  };

  constructor(private readonly httpClient: HttpClient) {
  }

  get<T>(uri: string): Observable<HttpResponse<T>> {
    return this.httpClient.get<HttpResponse<T>>(uri, this.httpOptions);
  }

  post<T>(uri: string, value: T): Observable<HttpResponse<T>> {
    return this.httpClient.post<HttpResponse<T>>(uri, value, this.httpOptions);
  }

  put<T>(uri: string, value: T): Observable<HttpResponse<T>> {
    return this.httpClient.put<HttpResponse<T>>(uri, value, this.httpOptions);
  }
}

@juan98mg
Copy link

juan98mg commented Sep 3, 2019

I also had this problem, but by removing the <T> from the get call I was able to get it to work using responseType: 'text'as 'text'.

Eg this does not work and returns the error:

const options: { responseType: 'text' as 'text', withCredentials: true };
this.httpClient.get<string>(url, options)

But this does work:

const options: { responseType: 'text' as 'text', withCredentials: true };
this.httpClient.get(url, options)

this solution works for me

@shunmugavalavan
Copy link

it doesn't work

const options= { 
        responseType:'arraybuffer'
      };
this.httpClient.get(url, options);

it does work

const options: any = {  //  The options with data type any does work well.
        responseType:'arraybuffer'
      };
this.httpClient.get(url, options);

@aipirvu
Copy link

aipirvu commented Sep 15, 2020

const httpOptions: Object = {
  responseType: 'blob'
};

it doesn't work

const options= { 
        responseType:'arraybuffer'
      };
this.httpClient.get(url, options);

it does work

const options: any = {  //  The options with data type any does work well.
        responseType:'arraybuffer'
      };
this.httpClient.get(url, options);

Declaring the options as any or Object is fine if you are only interested in setting the responseType and probably cover most of the cases for people who encounter this issue. However if you want to set something else (like headers) then typescript will no longer be able to help you with type checking. This is the reason why I was suggesting and I preferred using bracket notation to set the value for the responseType and only ts-ignore that line.

@sanioooook
Copy link

One of the solutions that helped me get rid of the compiler error(Type '"text"' is not assignable to type '"json"'.) was creating an HttpRequest using the constructor. And using http.request. I hope someone will help

@Lonli-Lokli
Copy link

@alxhub does it make sense to reiterate this issue with Typescript 4.1?

@evil-shrike
Copy link

In 2021 the API is still very weird:

Works:

return this.http.post<HttpResponse<Blob>>(this.getUrl(url), {
      responseType: 'blob',
      observe: 'response',
      headers: headers,
      params: params
    }).

Does not work (no matches):

return this.http.get<HttpResponse<Blob>>(this.getUrl(url), {
      responseType: 'blob',
      observe: 'response',
      headers: headers,
      params: params
    }).

have to use responseType: 'blob' as 'json',

@angular-robot angular-robot bot added the feature: under consideration Feature request for which voting has completed and the request is now under consideration label Jun 4, 2021
@sk4xd
Copy link

sk4xd commented Jan 26, 2022

Cast the httpOptions variable to type Object:

const httpOptions: Object = {
  responseType: 'blob'
};

as specified in the docs:https://angular.io/api/common/http/HttpClient#get

This actually helped me.

@alxhub alxhub added this to Inbox in Feature Requests via automation Feb 15, 2022
@alxhub alxhub moved this from Inbox to Needs Project Proposal in Feature Requests Feb 15, 2022
@noorsaifi
Copy link

Worked for me as suggested by @sk4xd .
download(filters: any) {
const httpOptions: Object = {
responseType: 'blob'
};
return this.httpClient.post<HttpResponse<Blob>>('/download', filters, httpOptions)
}

@simonthum
Copy link

Just my 2 cents:

Please make this a documentation ticket. Everybody is already confused. I just discussed this issue with colleagues.

get() has 15 overloads, 3 of which are generic.
request() has 17 overloads, 4 of which are generic.

I am yet to see a compiler that generates useful error messages in face of so many overloads.

The issue is that both responseType and observe are often required for proper resolution. This is hinted at in the docs of request(), but not get() or any other HTTP method. But even in request() docs are not really explicit about the requirement to select the right overload. Javascript devs typically do not pay much attention to overloading, and that's a big part of the user base.

Maybe I'm misreading this, but in here the observe is optional. But in many cases typescript needs the information for overload resolution.

I think the design intends to keep people on the success path in terms of useful types. I wouldn't "fix" that.

Just make it abundantly clear that the caller has to select the right overload, how to do so, and what to avoid.

@gustavostuff
Copy link

gustavostuff commented Mar 1, 2023

I tried the workaround mentioned almost at the top (from @alxhub ) but didn't work.

This is a very simple example.

get-example.png

EDIT: We're talking about this headers, right? If so, maybe my params object is not the right type.

EDIT 2: This is what the editor tells me about those 2:

EDIT 3: SO never mind, I think I was confused about usage. This worked:

return this.http.get(url, {
  withCredentials: false,
  responseType: 'text'
});

@Lonli-Lokli
Copy link

@alxhub I think that with Typescript 5.0 this issue can be finally resolved
https://devblogs.microsoft.com/typescript/announcing-typescript-5-0-rc/#const-type-parameters

@l-Legacy-l
Copy link

l-Legacy-l commented Jul 5, 2023

I was getting the same issue using a body and only this method and @rds-rafael one was working for me

Mine is:
const httpOptions: Object = { responseType: 'text' };
return this.http.post<string>(this.backendUrl + '/url', body, { params, ...httpOptions })

@Lonli-Lokli
Copy link

@l-Legacy-l
Use could remove string type from this.http.post<string>(...) call to make it work.
See this example.

For everyone - You should just carefully select the proper overload for your needs.

@JeanMeche
Copy link
Member

Since TS 3.4 it is possible to use a const assertion.

    const options = {headers: headers, params: params, responseType: 'text'} as const; 
    return this.http.get(url, options) //  Observable<string>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: common/http feature: under consideration Feature request for which voting has completed and the request is now under consideration feature Issue that requests a new feature
Projects
No open projects
Feature Requests
Needs Project Proposal
Development

Successfully merging a pull request may close this issue.