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

Allow passing misc data to http client options #18155

Open
mattlewis92 opened this issue Jul 16, 2017 · 56 comments · May be fixed by #20579
Open

Allow passing misc data to http client options #18155

mattlewis92 opened this issue Jul 16, 2017 · 56 comments · May be fixed by #20579

Comments

@mattlewis92
Copy link
Contributor

@mattlewis92 mattlewis92 commented Jul 16, 2017

I'm submitting a...


[ ] Regression (a behavior that used to work and stopped working in a new release)
[ ] Bug report  
[x] 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

Currently when using a http interceptor you cannot pass meta data about the request to the interceptor

Expected behavior

To be able to pass random data to the http interceptor. Currently the options object is so strongly typed that you cannot pass extra data to it. The proposal would be to add a meta (name needs bikeshedding) property with type any to the http request options that can be used for this purpose.

Minimal reproduction of the problem with instructions

N/A

What is the motivation / use case for changing the behavior?

// tell a caching interceptor to only cache if this is true
this.http.get('/path', {meta: {cache: true}})

// tell an interceptor not to add the `Authorization` for this request only
this.http.get('/path', {meta: {skipAuthorization: true}})

Environment


Angular version: 4.3.0


Browser:
- [x] Chrome (desktop) version 59
- [ ] Chrome (Android) version XX
- [ ] Chrome (iOS) version XX
- [ ] Firefox version XX
- [ ] Safari (desktop) version XX
- [ ] Safari (iOS) version XX
- [ ] IE version XX
- [ ] Edge version XX

Others:

@jnizet
Copy link
Contributor

@jnizet jnizet commented Jul 17, 2017

This would also be very useful for generic error interceptors:

// tell an interceptor that 404 error is expected and shouldn't be displayed
this.http.get('/path', {meta: {skipErrorHandling: resp => resp.status === 404}})

I see plenty of use-cases for this, and it was something possible in AngularJS using the options object.

@alxhub
Copy link
Contributor

@alxhub alxhub commented Jul 19, 2017

This has been on my mind as well. I think it's a good idea and something we should implement.

@mattlewis92
Copy link
Contributor Author

@mattlewis92 mattlewis92 commented Jul 19, 2017

@alxhub awesome! What do you think of the proposed API, can you think of anything better? I'd love to take a go at a PR for this 😀

@alxhub
Copy link
Contributor

@alxhub alxhub commented Jul 25, 2017

@mattlewis92 I like meta (or possibly metadata) as the key. In keeping with the theme for HttpRequest and HttpResponse it should also be immutable a la HttpHeaders / HttpParams.

I have an open task to merge the implementation of the two by extracting out a generic immutable map type. Once this is done, HttpMetadata could easily be built on top of that type.

@mattlewis92
Copy link
Contributor Author

@mattlewis92 mattlewis92 commented Jul 25, 2017

Ah ok I'll leave that to you to implement then 😄

@Agrumas
Copy link

@Agrumas Agrumas commented Aug 18, 2017

Is there any neat workaround for this? I am thinking about extending HttpRequest, however, overloaded constructors make implementation complicated.

@ChristianUlbrich
Copy link

@ChristianUlbrich ChristianUlbrich commented Aug 30, 2017

@Agrumas You could abuse HttpHeaders. Set them on your request with a nice name e.g. X-META-DATA and then strip them in the interceptor from the actual request before it gets send and re-add them on response. That way I plan to enable disabling individual interceptors on a per-request basis, e.g. for toggling a global LoadingNotification. But of course this feature needs to be added - because it has so many use cases.

@alxhub
Copy link
Contributor

@alxhub alxhub commented Aug 30, 2017

I have an in-progress branch for this, but it's unlikely to land before 5.0. Probably coming in 5.1.

@jbojcic1
Copy link

@jbojcic1 jbojcic1 commented Oct 14, 2017

@alxhub what's the status of this? I am really looking forward to it.

@sinedied
Copy link

@sinedied sinedied commented Nov 6, 2017

Really disappointed that with the release of Angular 5, the team deprecated the Http service without providing any way to perform with HttpClient what was possible with Http, including custom parameters.

We currently extends the Http service and added with 2 extra parameters to RequestOptionsArgs, for common use cases already cited here such as caching requests and error handling (see https://github.com/ngx-rocket/starter-kit/blob/master/src/app/core/http/http.service.ts for details).

We would like to move to HttpClient, but we cannot use interceptors because they do not support custom arguments, and we cannot extend HttpClient either because the request() method options do not use a type we can extend, see https://github.com/angular/angular/blob/master/packages/common/http/src/client.ts#L76-L83

So basically we are stuck with a deprecated API, and no way to move forward, am I missing something here @alxhub ?

@karptonite
Copy link
Contributor

@karptonite karptonite commented Nov 6, 2017

@sinedied I've been using HttpClient and adding metadata by abusing HTTP Headers, as suggested here.

In practice, it looks like this:

    if (req.headers.has('Skip-Prefix')) {
      const headers = req.headers.delete('Skip-Prefix');
      const directRequest = req.clone({ headers });
      return next.handle(directRequest);
    }
@sinedied
Copy link

@sinedied sinedied commented Nov 6, 2017

@karptonite I saw the comment on this, but this is really a ugly hack and I would like to avoid those while promoting best practices in our generator 😉

@karptonite
Copy link
Contributor

@karptonite karptonite commented Nov 6, 2017

@sinedied It isn't pretty, but I've seen far uglier hacks. You can extract out the addition of the headers into a utility function so that when the metadata is added for real, you only have to fix it in a couple places. Anyway, you aren't stuck with a deprecated (but not yet removed) API--you have at least one option.

@ChristianUlbrich
Copy link

@ChristianUlbrich ChristianUlbrich commented Nov 9, 2017

@sinedied For achieving this, you won't need to pass arbitrary params to the HttpClient (and thus to all interceptors); but you could wrap this behavior in a sugar method that clones your HttpService to a variant that is always caching with a fluent API like:

   this.httpService
        .enableCache()
        .get('http://google.com')

That way the implementation is hidden away and you should be prepared when HttpClient is going to be deprecated ...

Passing certain magic options along the request options looks like the bigger hack to me. Or:

It was fun while it lasted.

@wKoza
Copy link
Contributor

@wKoza wKoza commented Nov 9, 2017

Hi @alxhub , are you always ready to implement this feature in Angular 5.1 ?

wKoza added a commit to wKoza/angular that referenced this issue Nov 22, 2017
wKoza added a commit to wKoza/angular that referenced this issue Nov 22, 2017
wKoza added a commit to wKoza/angular that referenced this issue Nov 22, 2017
@rahul-winner
Copy link

@rahul-winner rahul-winner commented Sep 19, 2018

Are we submitting this PR to Angular 6 ?

@FDIM
Copy link
Contributor

@FDIM FDIM commented Sep 19, 2018

Given how much attention there was from contributors I'd say no. I just hope that it will land in 7.

@lazarljubenovic
Copy link
Contributor

@lazarljubenovic lazarljubenovic commented Sep 20, 2018

@alxhub According to the comments here, it seems like you've been working on this. There's also wKoza's PR which seems stale and another one is a bit newer, however it seems like not all tests are passing.

Do you happen to have info on the progress? It's been almost a year since the last update.

@wKoza
Copy link
Contributor

@wKoza wKoza commented Sep 20, 2018

I can update this PR if necessary. Otherwise, I worked on an another PR which allows having multiple instances of HttpClient.

@FDIM
Copy link
Contributor

@FDIM FDIM commented Sep 20, 2018

@lazarljubenovic The tests that are not passing are flaky ones and unrelated to the changes I did. Aside of that the solution in my PR is type safe :)

@dawidgarus
Copy link

@dawidgarus dawidgarus commented Sep 20, 2018

@FDIM your solution is a little bit overengineered.
Something like this would be much simpler, less verbose and still type safe:

export class CacheInterceptorOptions {
  constructor(public cache: boolean) {}
}

@Injectable()
export class CacheInterceptor { 
  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const cacheOptions = req.getMeta(CacheInterceptorOptions);
    if (cacheOptions && cacheOptions.cache) { ... }
  }
}

and usage:

this.http.get('/path', { meta: [new CacheInterceptorOptions(true)] })
@sebastian-zarzycki-es
Copy link

@sebastian-zarzycki-es sebastian-zarzycki-es commented Oct 18, 2018

So, when can we get the functionality that is available since Angular 1.x, basically for years now, in modern Angular? It seems bizarre that adding a simple field, heck, make it untyped, for what I care, is causing so much discussion and trouble. Just add it, for pete's sake. If you think it's wrong solution, then what's stopping you from switching to new one ONCE it is ready? But why oh why we cannot have the simple version now, WHILE we are waiting?

@lazarljubenovic
Copy link
Contributor

@lazarljubenovic lazarljubenovic commented Oct 18, 2018

adding a simple field, heck, make it untyped, for what I care,

Because we care too.

But why oh why we cannot have the simple version now, WHILE we are waiting?

Because it's a recipe for a disaster.

@FDIM
Copy link
Contributor

@FDIM FDIM commented Oct 18, 2018

Solution from @dawidgarus is nice as well, how could we get some attention to this issue from someone that could make a decision and also has permission to merge? I am not picky for verbosity as long as it is type safe.

@sebastian-zarzycki-es
Copy link

@sebastian-zarzycki-es sebastian-zarzycki-es commented Oct 18, 2018

Yeah, ok, disaster. Sigh. Wake me up same time next year. Maybe, by then, we will be able to finally have these advanced pro enterprise features, like animating a modal window.

@jburtondev
Copy link

@jburtondev jburtondev commented Nov 1, 2018

Hi @alxhub, do we have an update for this?

Blackbaud-PaulCrowder added a commit to blackbaud/skyux-http that referenced this issue Nov 8, 2018
The implementation contains a workaround for passing additional context to the interceptor since Angualr doesn't currently support it.  Here is an issue describing the problem: angular/angular#18155
Blackbaud-PaulCrowder added a commit to blackbaud/skyux-http that referenced this issue Nov 14, 2018
* Created auth interceptor for use with Angular's HttpClient

The implementation contains a workaround for passing additional context to the interceptor since Angualr doesn't currently support it.  Here is an issue describing the problem: angular/angular#18155

* Formatted imports/exports

* Removed unused parameter

* Added comment to code coverage suppression

* Created `SkyAuthHttpClientModule`
@dhilgarth
Copy link

@dhilgarth dhilgarth commented Dec 18, 2018

Based on the suggestions in this issue I have created a simple solution that can be used until there is first class support for this in Angular: https://gist.github.com/dhilgarth/783ed994be6e3af0c236fc36f327c9cd
Maybe it helps someone. Feedback welcome.

@Agrumas
Copy link

@Agrumas Agrumas commented Feb 2, 2019

Huh, #25751 attracted some attention, fingers crossed 2019 will be a good year and this feature lands

@bh3605
Copy link

@bh3605 bh3605 commented Jul 12, 2019

I'm working on a workaround that extends off of the base HttpRequest object where additional properties are defined.

export class HttpRequestExtended<T> extends HttpRequest<T> {
public needAssessmentsToken: boolean;
public service: any;
public needAuthToken: boolean;

public constructor(method: 'DELETE' | 'GET' | 'HEAD' | 'JSONP' | 'OPTIONS' | 'POST' | 'PUT' | 'PATCH',
                   service: any,
                   body: T | null,
                   init?: {
                       headers?: HttpHeaders;
                       reportProgress?: boolean;
                       params?: HttpParams;
                       responseType?: 'arraybuffer' | 'blob' | 'json' | 'text';
                       withCredentials?: boolean;
                   }
) {
    super(method, service.url, body, init);
    this.service = service;
    this.needAssessmentsToken = service.needAssessmentsToken;
    this.needAuthToken = service.needAuthToken;
}`

You then have a "wrapper" where this HttpRequest subclass is typesafe

export class HttpClientExtended {
public constructor(private httpClient: HttpClient) {
}
  /**
   * GET request
   * @param {string} endPoint backend endpoint to call
   * @param {IRequestOptions} options request options (headers, body, etc.)
   * @returns {Observable<T>}
   */
  public get<T>(arr: any, options?: RequestOptions): any {
    const request: HttpRequestExtended<any> = new HttpRequestExtended('GET', arr, null, options);
    return this.httpClient.request(request).pipe(
      filter((event: HttpEvent<any>) => event instanceof HttpResponse),
      map((response: HttpResponse<any>) => response.body));
  }

Now your service looks like

constructor(private httpClient: HttpClientExtended) {
}

private getItems(): Observable<any> {
    return this.httpClient.get({ url: 'my-url', needAuthToken: true});
}

In my actual implementation that object is actually a getter property on an object that setups the appropriate url and whatever custom data I want to pass to my interceptors

Now your intercept method in your Interceptor looks like:

intercept(req: HttpRequestExtended<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if (req.needAuthToken) {

One thing to point out is I have to override HttpRequest's clone method so my custom properties remain any time an interceptor calls clone() to add or remove HttpHeaders

  clone(update: {
headers?: HttpHeaders,
reportProgress?: boolean,
params?: HttpParams,
responseType?: 'arraybuffer'|'blob'|'json'|'text',
withCredentials?: boolean,
body?: any|null,
method?: string,
url?: string,
setHeaders?: {[name: string]: string | string[]},
setParams?: {[param: string]: string};
 } = {}): HttpRequestExtended<any> {
  var newRequest = super.clone(update);
  return new HttpRequestExtended(newRequest.method as 'DELETE' | 'GET' | 'HEAD' | 'JSONP' | 'OPTIONS' | 'POST' | 'PUT' | 'PATCH', this.service, newRequest.body, Object.assign({}, newRequest));
}

In my approach I'm choosing to pass in an HttpRequest object, but HttpClient handles HttpRequest objects differently from a url, body, options parameters. Why are they treated so differently? This is causing me to essentially rewrite the response type switch case in HttpClient's request method (which isn't shown above since I haven't written it yet).

This difference left me wondering if a different approach would be to allow me to pass in an HttpRequest object with a property that determines whether I want to listen to the HttpEvent stream or the response body itself. By default, passing in an HttpRequest object will make your subscriber receive HttpEvent events, but I only care about the response body. HttpClient wouldn't have to recreate the HttpRequest object thus preserving any custom properties I have defined. I also wouldn't need the HttpClientExtended class anymore since I'd be able to pass in any HttpRequest type I want. Possible downsides are you have to create a new class extending off of the HttpRequest type and override the clone method to preserve your custom properties.

This proposed solution gives developers finer control over how they want to handle the response when an HttpRequest object is passed and allows interceptors to handle defined custom properties on subclasses of HttpRequest.

@maxandriani
Copy link

@maxandriani maxandriani commented Nov 24, 2019

Hi guys. Do we have some news about this issue?

@ricardo-mello
Copy link

@ricardo-mello ricardo-mello commented Nov 24, 2019

Based on previous solutions, I did an HttpService that extends HttpClient and use it on my entire application:

https://gist.github.com/ricardo-mello/849a205ef17c2754ba819908f6fd4efb

Hope it helps someone 😄

@tommck
Copy link

@tommck tommck commented Feb 27, 2020

Really sucks that we're STILL left with the choice of hacking headers or wrapping the HttpClient

mmebsout added a commit to mmebsout/DataCube that referenced this issue Jul 15, 2020
mmebsout added a commit to mmebsout/DataCube that referenced this issue Jul 15, 2020
@aksdevac
Copy link

@aksdevac aksdevac commented Jul 15, 2020

This should be added in v11.

@sebastian-zarzycki-es
Copy link

@sebastian-zarzycki-es sebastian-zarzycki-es commented Jul 15, 2020

This should be (re)added in v2.

@CesarD
Copy link

@CesarD CesarD commented Jul 23, 2020

I can't believe we're still missing this SO REALLY BASIC feature! This is something even AngularJs had!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

You can’t perform that action at this time.