Skip to the solution
MediumFrontend9 min read

What is an HTTP interceptor?

314 views

The problem

Question presented to candidate: "Every request from our Angular app needs an auth header, and we want to log how long each API call takes, without touching the forty services that call HttpClient. What is an HTTP interceptor and how would you use one here?"

What a strong answer should cover:

  • An interceptor is middleware in HttpClient's pipeline: it sees every outgoing HttpRequest and every incoming HttpEvent, and can change, replace or short-circuit either.
  • Modern form: an HttpInterceptorFn (req, next) => next(req), registered with provideHttpClient(withInterceptors([...])); it runs in an injection context, so inject() works.
  • Requests are immutable: change them with req.clone({ setHeaders: ... }).
  • Order: requests flow through interceptors in array order, responses come back in reverse order; an interceptor that does not call next ends the chain (fake backends, caches).
  • Legacy class interceptors (HttpInterceptor + HTTP_INTERCEPTORS) need withInterceptorsFromDi(); both kinds can coexist, ordered by where the features are listed.
  • Only requests made with HttpClient are intercepted.

Clarifying questions expected:

  • "Should the token go to every URL, or only our own API?"
  • "Is anything else already registered as an interceptor, such as a library's?"

Code / implementation expected: Yes β€” an auth interceptor, a timing interceptor and a fake backend, with a log of the order they run in.

  • http
  • interceptor

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

9 min read Β· 10 sections

Read the code

AngularAuth, timing and fake-backend functional interceptors with an order log
import { ApplicationConfig, Component, Injectable, inject, signal } from "@angular/core";
import { HttpClient, HttpInterceptorFn, HttpResponse, provideHttpClient, withInterceptors } from "@angular/common/http";
import { delay, of, tap } from "rxjs";

@Injectable({ providedIn: "root" })
export class TokenStore {
  token = "abc123";
}

// 1. Adds a header. Requests are immutable, so clone() with the change.
export const authInterceptor: HttpInterceptorFn = (req, next) => {
  console.log(`  auth    -> ${req.method} ${req.url}`);
  const token = inject(TokenStore).token; // interceptors run in an injection context
  const authed = req.clone({ setHeaders: { Authorization: `Bearer ${token}` } });
  return next(authed).pipe(tap((ev) => ev instanceof HttpResponse && console.log(`  auth    <- ${ev.status}`)));
};

// 2. Logs how long the request took.
export const timingInterceptor: HttpInterceptorFn = (req, next) => {
  console.log(`  timing  -> sees Authorization: ${req.headers.get("Authorization")}`);
  const started = performance.now();
  return next(req).pipe(
    tap((ev) => ev instanceof HttpResponse &&
      console.log(`  timing  <- ${ev.status} after ~${Math.round((performance.now() - started) / 10) * 10}ms`)),
  );
};

// 3. A fake backend: it never calls next(), so no real network request is made.
export const fakeBackend: HttpInterceptorFn = (req) => {
  console.log(`  backend    answering ${req.url}`);
  return of(new HttpResponse({ status: 200, body: [{ id: 1, name: "Ada" }] })).pipe(delay(30));
};

export const appConfig: ApplicationConfig = {
  providers: [
    // Order in this array = order requests pass through. Responses come back in reverse.
    provideHttpClient(withInterceptors([authInterceptor, timingInterceptor, fakeBackend])),
  ],
};

@Component({
  selector: "app-root",
  template: `<button (click)="load()">Load users</button> <p>{{ users() }}</p>`,
})
export class AppComponent {
  private http = inject(HttpClient);
  users = signal("not loaded");
  load() {
    console.log("http.get('/api/users')");
    this.http.get<{ name: string }[]>("/api/users").subscribe((list) => {
      console.log("  component got the body");
      this.users.set(list.map((u) => u.name).join(", "));
    });
  }
}

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 70 of 121 in Angular

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All Angular questions
Up nextMedium

What are pipes in Angular?

Continue