Following are a number of common uses for interceptors.
Apps often use an interceptor to set default headers on outgoing requests.
The sample app has an AuthService
that produces an authorization token. Here is its AuthInterceptor
that injects that service to get the token and adds an authorization header with that token to every outgoing request:
import { AuthService } from '../auth.service'; @Injectable() export class AuthInterceptor implements HttpInterceptor { constructor(private auth: AuthService) {} intercept(req: HttpRequest<any>, next: HttpHandler) { // Get the auth token from the service. const authToken = this.auth.getAuthorizationToken(); // Clone the request and replace the original headers with // cloned headers, updated with the authorization. const authReq = req.clone({ headers: req.headers.set('Authorization', authToken) }); // send cloned request with header to the next handler. return next.handle(authReq); } }
The practice of cloning a request to set new headers is so common that there's a setHeaders
shortcut for it:
// Clone the request and set the new header in one step. const authReq = req.clone({ setHeaders: { Authorization: authToken } });
An interceptor that alters headers can be used for a number of different operations, including:
If-Modified-Since
Because interceptors can process the request and response together, they can perform tasks such as timing and logging an entire HTTP operation.
Consider the following LoggingInterceptor
, which captures the time of the request, the time of the response, and logs the outcome with the elapsed time with the injected MessageService
.
import { finalize, tap } from 'rxjs/operators'; import { MessageService } from '../message.service'; @Injectable() export class LoggingInterceptor implements HttpInterceptor { constructor(private messenger: MessageService) {} intercept(req: HttpRequest<any>, next: HttpHandler) { const started = Date.now(); let ok: string; // extend server response observable with logging return next.handle(req) .pipe( tap({ // Succeeds when there is a response; ignore other events next: (event) => (ok = event instanceof HttpResponse ? 'succeeded' : ''), // Operation failed; error is an HttpErrorResponse error: (error) => (ok = 'failed') }), // Log when response observable either completes or errors finalize(() => { const elapsed = Date.now() - started; const msg = `${req.method} "${req.urlWithParams}" ${ok} in ${elapsed} ms.`; this.messenger.add(msg); }) ); } }
The RxJS tap
operator captures whether the request succeeded or failed. The RxJS finalize
operator is called when the response observable either returns an error or completes and reports the outcome to the MessageService
.
Neither tap
nor finalize
touch the values of the observable stream returned to the caller.
Interceptors can be used to replace the built-in JSON parsing with a custom implementation.
The CustomJsonInterceptor
in the following example demonstrates how to achieve this. If the intercepted request expects a 'json'
response, the responseType
is changed to 'text'
to disable the built-in JSON parsing. Then the response is parsed via the injected JsonParser
.
// The JsonParser class acts as a base class for custom parsers and as the DI token. @Injectable() export abstract class JsonParser { abstract parse(text: string): any; } @Injectable() export class CustomJsonInterceptor implements HttpInterceptor { constructor(private jsonParser: JsonParser) {} intercept(httpRequest: HttpRequest<any>, next: HttpHandler) { if (httpRequest.responseType === 'json') { // If the expected response type is JSON then handle it here. return this.handleJsonResponse(httpRequest, next); } else { return next.handle(httpRequest); } } private handleJsonResponse(httpRequest: HttpRequest<any>, next: HttpHandler) { // Override the responseType to disable the default JSON parsing. httpRequest = httpRequest.clone({responseType: 'text'}); // Handle the response using the custom parser. return next.handle(httpRequest).pipe(map(event => this.parseJsonResponse(event))); } private parseJsonResponse(event: HttpEvent<any>) { if (event instanceof HttpResponse && typeof event.body === 'string') { return event.clone({body: this.jsonParser.parse(event.body)}); } else { return event; } } }
You can then implement your own custom JsonParser
. Here is a custom JsonParser that has a special date reviver.
@Injectable() export class CustomJsonParser implements JsonParser { parse(text: string): any { return JSON.parse(text, dateReviver); } } function dateReviver(key: string, value: any) { /* . . . */ }
You provide the CustomParser
along with the CustomJsonInterceptor
.
{ provide: HTTP_INTERCEPTORS, useClass: CustomJsonInterceptor, multi: true }, { provide: JsonParser, useClass: CustomJsonParser },
Interceptors can handle requests by themselves, without forwarding to next.handle()
.
For example, you might decide to cache certain requests and responses to improve performance. You can delegate caching to an interceptor without disturbing your existing data services.
The CachingInterceptor
in the following example demonstrates this approach.
@Injectable() export class CachingInterceptor implements HttpInterceptor { constructor(private cache: RequestCache) {} intercept(req: HttpRequest<any>, next: HttpHandler) { // continue if not cacheable. if (!isCacheable(req)) { return next.handle(req); } const cachedResponse = this.cache.get(req); return cachedResponse ? of(cachedResponse) : sendRequest(req, next, this.cache); } }
The isCacheable()
function determines if the request is cacheable. In this sample, only GET requests to the package search API are cacheable.
If the request is not cacheable, the interceptor forwards the request to the next handler in the chain
If a cacheable request is found in the cache, the interceptor returns an of()
observable with the cached response, by-passing the next
handler and all other interceptors downstream
If a cacheable request is not in cache, the code calls sendRequest()
. This function forwards the request to next.handle()
which ultimately calls the server and returns the server's response.
/** * Get server response observable by sending request to `next()`. * Will add the response to the cache on the way out. */ function sendRequest( req: HttpRequest<any>, next: HttpHandler, cache: RequestCache): Observable<HttpEvent<any>> { return next.handle(req).pipe( tap(event => { // There may be other events besides the response. if (event instanceof HttpResponse) { cache.put(req, event); // Update the cache. } }) ); }
Notice how
sendRequest()
intercepts the response on its way back to the application. This method pipes the response through thetap()
operator, whose callback adds the response to the cache.The original response continues untouched back up through the chain of interceptors to the application caller.
Data services, such as
PackageSearchService
, are unaware that some of theirHttpClient
requests actually return cached responses.
The HttpClient.get()
method normally returns an observable that emits a single value, either the data or an error. An interceptor can change this to an observable that emits multiple values.
The following revised version of the CachingInterceptor
optionally returns an observable that immediately emits the cached response, sends the request on to the package search API, and emits again later with the updated search results.
// cache-then-refresh if (req.headers.get('x-refresh')) { const results$ = sendRequest(req, next, this.cache); return cachedResponse ? results$.pipe( startWith(cachedResponse) ) : results$; } // cache-or-fetch return cachedResponse ? of(cachedResponse) : sendRequest(req, next, this.cache);
The cache-then-refresh option is triggered by the presence of a custom
x-refresh
header.A checkbox on the
PackageSearchComponent
toggles awithRefresh
flag, which is one of the arguments toPackageSearchService.search()
. Thatsearch()
method creates the customx-refresh
header and adds it to the request before callingHttpClient.get()
.
The revised CachingInterceptor
sets up a server request whether there's a cached value or not, using the same sendRequest()
method described above. The results$
observable makes the request when subscribed.
results$
.results$
. This produces a recomposed observable that emits two responses, so subscribers will see a sequence of these two responses:
© 2010–2023 Google, Inc.
Licensed under the Creative Commons Attribution License 4.0.
https://angular.io/guide/http-interceptor-use-cases