103

Usually it's desirable to have default timeout (e.g. 30s) that will be applied to all requests and can be overridden for particular longer requests (e.g. 600s).

There's no good way to specify default timeout in Http service, to my knowledge.

What is the way to approach this in HttpClient service? How to define a default timeout for all outgoing requests, that can be overriden for specific ones?

3
  • 1
    @neuhaus its not angularjs it angular , not a duplicate Commented Aug 29, 2017 at 12:19
  • you can make use of timeout operator here ? Commented Aug 29, 2017 at 12:20
  • @RahulSingh This is the way it was done in Http, and this approach required to specify .timeout(...) for each request, not by default. Commented Aug 29, 2017 at 12:22

6 Answers 6

198
+100

It appears that without extending HttpClientModule classes, the only expected ways for interceptors to communicate with respective requests are params and headers objects.

Since timeout value is scalar, it can be safely provided as a custom header to the interceptor, where it can be decided if it's default or specific timeout that should be applied via RxJS timeout operator:

import { Inject, Injectable, InjectionToken } from '@angular/core';
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { Observable } from 'rxjs';
import { timeout } from 'rxjs/operators';
    
export const DEFAULT_TIMEOUT = new InjectionToken<number>('defaultTimeout');
    
@Injectable()
export class TimeoutInterceptor implements HttpInterceptor {
  constructor(@Inject(DEFAULT_TIMEOUT) protected defaultTimeout: number) {
  }
    
  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const timeoutValue = req.headers.get('timeout') || this.defaultTimeout;
    const timeoutValueNumeric = Number(timeoutValue);

    return next.handle(req).pipe(timeout(timeoutValueNumeric));
  }
}

This can be configured in your app module like:

providers: [
  [{ provide: HTTP_INTERCEPTORS, useClass: TimeoutInterceptor, multi: true }],
  [{ provide: DEFAULT_TIMEOUT, useValue: 30000 }]
],

The request is then done with a custom timeout header

http.get('/your/url/here', { headers: new HttpHeaders({ timeout: `${600000}` }) });

Since headers are supposed to be strings, the timeout value should be converted to a string first.

Here is a demo.

Credits go to @RahulSingh and @Jota.Toledo for suggesting the idea of using interceptors with timeout.

The solution explains how to specify default timeout (30s) and override it for particular requests (600s). By default there is no timeout in HttpClient. In case there's already one, this means the cause is different, most likely a webserver, which is not uncommon, e.g. Angular CLI devserver. This is a different problem that cannot be solved from client side, regardless of Angular or any other client-side framework.

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

19 Comments

@Jota.Toledo I don't think observables alone can help here. Once an observable was chained with.timeout(defaultTimeout) operator inside the interceptor, it's impossible to 'cancel' it and chain with .timeout(customTimeout) instead. This is probably possible by subclassing Observable but it will be cumbersome and fragile too. Hope this will be fixed someday in HttpClient itself. AngularJS $http had timeout option and it worked just great.
Yup, Im aware of that. I tried to implement something with mergeMap and other operators based in a SO answer, but In one of the cases (I think default overriden by larger time) my approach didnt work. I will choose your approach despite the fact that I dont like the use of HttpHeader for comunication with the interceptor, but in the current state of the API I think there is no better approach.
this code doesn't work to increase timeout request to be bigger than 30s. If you set it to 60 seconds, the angular's default 30 seconds will be applied
@user1034912 Afaik yes, Angular doesn't provide anything more specific to implement this
@Thompson is correct : this code does NOT work. Setting the observable timeout does not have any effect on the timeout of the HttpClient. The feature has been requested but not selected for implementation (github.com/angular/angular/issues/34421). The only solution that is valid for now is to go for an asynchronous solution. Trigger the processing, and come back later when it is complete.
|
31

In complement to the other answers, just beware that if you use the proxy config on the dev machine, the proxy's default timeout is 120 seconds (2 minutes). For longer requests, you'll need to define a higher value in the configuration, or else none of these answers will work. The timeout must be set in milliseconds (it's set to 6 minutes in the example below).

{
  "/api": {
    "target": "http://localhost:3000",
    "secure": false,
    "timeout": 360000
  }
}

7 Comments

Thanks for adding this, I only realized I was using a proxy when I read this answer!
Thanks for the awesome answer. May I know where that default timeout value is documented? I can't find it either in angular nor webpack documentation.
Thanks for this wonderful answer i understood this when only i see this one
Should this be done on the development server ?
This does not work on production server. any solution ?
|
15

You could create a global interceptor with the base timeout value as follows:

import { Injectable } from '@angular/core';
import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest} from '@angular/common/http';

@Injectable()
export class AngularInterceptor implements HttpInterceptor {
  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return next.handle(req).timeout(30000, Observable.throw("Request timed out"));
    // 30000 (30s) would be the global default for example
  }
}

Afterwards you need to register this injectable in the providers array of you root module.

The tricky part would be to override the default time (increase/decrease) for specific requests. For the moment I dont know how to solve this.

1 Comment

@Octave this snippet is 7 years old, lol
15

Using the new HttpClient you can try some thing like this

@Injectable()
export class AngularInterceptor implements HttpInterceptor {
  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return next.handle(req).timeout(5000).do(event => {}, err => { // timeout of 5000 ms
        if(err instanceof HttpErrorResponse){
            console.log("Error Caught By Interceptor");
            //Observable.throw(err);
        }
    });
  }
}

Adding a timeout to the next.handle(req) which is passed on.

Registering it in AppModule like

@NgModule({
    declarations: [
        AppComponent
    ],
    imports: [
        BrowserModule,
        HttpClientModule
    ],
    providers: [
        [ { provide: HTTP_INTERCEPTORS, useClass: 
              AngularInterceptor, multi: true } ]
    ],
    bootstrap: [AppComponent]
})
export class AppModule {
}

2 Comments

Thanks. However, as mentioned in another answer, it's seems to be impossible to increase timeout for particular request this way.
@estus yes that is the hard part as of now I don't think there is any direct solutions to it might have a work around. Will see to it. The other answer was not much different. Just added a throw after time out
1

I found only one solution by override default XhrFactory and use some service with timeout value. You can use this approach for apply timeout for several requests, or you can use only TimeoutXhrFactory with hardcoded timeout for all requests:

TimeoutXhrFactory

import { XhrFactory } from '@angular/common';
import { Injectable } from '@angular/core';
import { TimeoutXhrService } from './timeout-xhr.service';

@Injectable()
export class TimeoutXhrFactory implements XhrFactory {
    constructor(private timeoutXhrService: TimeoutXhrService) {}

    build(): XMLHttpRequest {
        const xhr = new XMLHttpRequest();
        const timeout = this.timeoutXhrService.getHTTPTimeout();
        if (timeout !== 0) {
            xhr.addEventListener(
                'readystatechange',
                function onReadyStateChange() {
                    if (xhr.readyState === XMLHttpRequest.OPENED) {
                        xhr.timeout = timeout; // you can use here hardcoded value for all requests, and remove usage of timeoutXhrService
                        this.timeoutXhrService.resetHTTPTimeout();
                        xhr.removeEventListener('readystatechange', onReadyStateChange);
                    }
                }.bind(this),
                false,
            );
        }

        return xhr;
    }
}

TimeoutXhrService

import { Injectable } from '@angular/core';

@Injectable()
export class TimeoutXhrService {
  private _currentHTTPTimeout = 0;

  public setHTTPTimeout(timeout: number) {
    this._currentHTTPTimeout = timeout;
  }

  public getHTTPTimeout(): number {
    return this._currentHTTPTimeout;
  }

  public resetHTTPTimeout() {
    this._currentHTTPTimeout = 0;
  }
}

InterceptorsModule

@NgModule({
    imports: [HttpClientModule, SharedModule, CoreModule],
    providers: [
        TimeoutXhrService,
        {
            provide: XhrFactory,
            useClass: TimeoutXhrFactory,
        },
    ],
})
export class InterceptorsModule {}

finally for request with specific timeot:

doRequest(formData: FormData, params: Record<string, any>): Observable<any> {
        this.timeoutXhrService.setHTTPTimeout(120000);
        return this.http.post<any>('url', formData, {
            params
        });
    }

Comments

0

add rxjs timeout operator to your httpclient call - .pipe(timeout(60000))

return this.httpClient.post<ResponseType>(UrlProvider.fullUrl(url), body, combinedOptions).pipe(timeout(60000));

2 Comments

Does not work for timeouts greater than the default (30 in my CHrome browser)
30ms? thats a very short timeout

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.