Skip to the solution
MediumFrontend9 min read

What is provideHttpClient(withInterceptors([...])) and how does it replace legacy HTTP interceptor providing?

398 views

The problem

Question presented to candidate: "Our app still has HttpClientModule in an NgModule and four class interceptors registered with HTTP_INTERCEPTORS. We are going standalone. What replaces that setup, how do you migrate without breaking anything, and what happens while both styles exist?"

What a strong answer should cover:

  • The legacy setup: HttpClientModule plus { provide: HTTP_INTERCEPTORS, useClass: X, multi: true } for each class implementing HttpInterceptor.
  • The replacement: provideHttpClient(withInterceptors([fnA, fnB])) with HttpInterceptorFn functions; ordering is one explicit array.
  • The bridge: withInterceptorsFromDi() keeps class interceptors working under provideHttpClient(); without it they are silently ignored.
  • When mixed, the position of the features in the provideHttpClient(...) call decides whether classes or functions run first.
  • Status in 22.2.3: HttpClientModule and HttpClientTestingModule are deprecated; DI-based interceptors "may be phased out in a later release".
  • A safe migration order: switch the module to provideHttpClient(withInterceptorsFromDi()), then convert classes one at a time, then drop the bridge.

Clarifying questions expected:

  • "Do any interceptors depend on registration order today?"
  • "Are there interceptors provided by third-party NgModules we cannot rewrite?"

Code / implementation expected: Yes β€” a class interceptor and its functional equivalent registered side by side, showing the resulting order.

  • http
  • di

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 Β· 9 sections

Read the code

AngularA class interceptor and its functional twin, registered together mid-migration
import { ApplicationConfig, Component, Injectable, inject, signal } from "@angular/core";
import {
  HTTP_INTERCEPTORS, HttpClient, HttpEvent, HttpHandler, HttpInterceptor, HttpInterceptorFn,
  HttpRequest, HttpResponse, provideHttpClient, withInterceptors, withInterceptorsFromDi,
} from "@angular/common/http";
import { Observable, of } from "rxjs";

export const seen: string[] = [];

@Injectable({ providedIn: "root" })
export class TenantService {
  id = "acme";
}

// BEFORE: a class-based interceptor, registered through the HTTP_INTERCEPTORS multi-provider.
@Injectable()
export class LegacyTenantInterceptor implements HttpInterceptor {
  constructor(private tenant: TenantService) {}
  intercept(req: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
    seen.push("legacy class");
    return next.handle(req.clone({ setHeaders: { "X-Tenant": this.tenant.id } }));
  }
}

// AFTER: the same job as a function. inject() replaces the constructor, next() replaces next.handle().
export const tenantInterceptor: HttpInterceptorFn = (req, next) => {
  seen.push("functional");
  return next(req.clone({ setHeaders: { "X-Tenant": inject(TenantService).id } }));
};

const fakeBackend: HttpInterceptorFn = (req) =>
  of(new HttpResponse({ status: 200, body: req.headers.get("X-Tenant") }));

// Mid-migration: both styles at once. The order of the two features decides who runs first.
export const appConfig: ApplicationConfig = {
  providers: [
    provideHttpClient(withInterceptorsFromDi(), withInterceptors([tenantInterceptor, fakeBackend])),
    { provide: HTTP_INTERCEPTORS, useClass: LegacyTenantInterceptor, multi: true },
  ],
};

@Component({
  selector: "app-root",
  template: `<button (click)="send()">Send</button> <p>{{ out() }}</p>`,
})
export class AppComponent {
  private http = inject(HttpClient);
  out = signal("");

  send() {
    seen.length = 0;
    this.http.get<string>("/api/orders").subscribe((tenant) => {
      this.out.set(`X-Tenant=${tenant} | order: ${seen.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 64 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 is a service and why use it?

Continue