There actually is a way to have an interceptor for a specific service. Notice the emphasis, because it's not really an implementation of the HttpInterceptor
interface.
TL:DR - jump to examples at the bottom.
The HttpClient
actually only converts the input through all the possible methods into a single structure called HttpRequest
, which is then being passed to an HttpHandler
implementation. That, as the name suggests, is responsible for handling the HTTP requests.
Among HttpHandler
implementations you'd find e.g. HttpInterceptingHandler
(runs the chain of interceptors) or HttpXhrBackend
(inherited through the HttpBackend
class, this handler is the last as it actually sends the XHR request). If you take a look in the source code of the former class, you'll see that it's actually depending on the latter (indirectly, under the HttpBackend
token, HttpXhrBackend
is provided as default implementation). So just like the interceptors, Angular is chaining the handlers through DI, where the last one is the backend executing the request.
What you can do is adding another handler to this chain of handlers, ideally as the first one. For that you will have to define a class extending HttpHandler
, injecting the first implementation of HttpHandler
(by first I mean the implementation, which is provided under the HttpHandler
token) and just call-through in the handle
method after you're done.
At last, since the HTTP client is an Angular service, it may be shared among more components, directives, services etc., so you will want to create your own instance to avoid that and keep your interceptor isolated. The HttpClient constructor requires an HttpHandler
instance, where you pass your own implementation.
See my simple example for handling authentication in such handler:
@Injectable()
export class AuthHandler extends HttpHandler {
constructor(
private readonly auth: AuthService, // custom service providing the auth token
private readonly next: HttpHandler // injects the "default" handler -> HttpInterceptingHandler
) {
super();
}
/** @override */ handle(req: HttpRequest<any>): Observable<HttpEvent<any>> {
// do whatever you need on the request
// because of immutability cloning is required
const clone = req.clone({
setHeaders: { 'Authorization': 'Bearer ' + auth.getToken() }
});
// pass the request to the next handler, eventually ending up in HttpXhrBackend
return this.next.handle(clone);
}
}
And the usage of the handler in that one specific service:
export class HeroService {
protected readonly http: HttpClient;
constructor(auth: AuthHandler) {
// create your own instance with you custom handler
this.http = new HttpClient(auth);
}
async getHero(id: string): Hero {
// every request made through such client goes to the handler for processing
await this.http.get<Hero>(`/api/heroes/${id}`).toPromise();
}
}
This code is working for me on Angular 8.
I hope this helps other lost souls searching for a solution.
EDIT 2023-06-20: Fixed link to Angular code in GitHub.
HttpXhrBackend
) is replaced with a testing class. I just verified it in my code. – Healey