skip to content

In a standalone Angular 22 app, how is HttpClient made available, and when do you still need provideHttpClient()?

level: juniorimportance: must knowfreq 66%

answer

  1. available in root since v21
  2. features are the reason to configure
  3. with-functions passed as arguments
  4. environment providers, not component ones
  5. the old NgModule is deprecated

basics

~10 s

Since Angular 21, HttpClient is provided in root, so inject(HttpClient) works with no setup. provideHttpClient() in the application providers is still how you add features: withInterceptors, withInterceptorsFromDi, withXsrfConfiguration or withNoXsrfProtection, withXhr and withRequestsMadeViaParent.

solid answer

~30 s

`HttpClient` has been **provided in root** since Angular 21, so a service can `inject(HttpClient)` without any configuration and gets the defaults: in v22 the `FetchBackend` and the built-in XSRF interceptor. You call `provideHttpClient(...)` in `app.config.ts` (or an `NgModule`'s or a route's `providers`) when you need to **change** that configuration, by passing feature functions: `withInterceptors([...])` for functional interceptors, `withInterceptorsFromDi()` for class-based ones, `withXsrfConfiguration({...})` or `withNoXsrfProtection()`, `withXhr()` for the XMLHttpRequest backend, and `withRequestsMadeViaParent()` for nested injectors. It returns `EnvironmentProviders`, so it belongs in application or route providers, not in a component's `providers`. `HttpClientModule` is deprecated and equals `provideHttpClient(withInterceptorsFromDi(), withXhr())`.

code

ts · 31 lines
ts
// app.config.ts
import {ApplicationConfig} from '@angular/core';
import {provideHttpClient, withInterceptors, withXsrfConfiguration} from '@angular/common/http';
import {authInterceptor} from './auth.interceptor';

export const appConfig: ApplicationConfig = {
  providers: [
    provideHttpClient(
      withInterceptors([authInterceptor]),
      withXsrfConfiguration({cookieName: 'CSRF-TOKEN', headerName: 'X-CSRF-TOKEN'}),
    ),
  ],
};

// files.service.ts: HttpClient is injected the same way with or without provideHttpClient()
import {HttpClient} from '@angular/common/http';
import {inject, Injectable} from '@angular/core';

export interface StoredFile {
  id: string;
  name: string;
}

@Injectable({providedIn: 'root'})
export class FilesService {
  private readonly http = inject(HttpClient);

  list() {
    return this.http.get<StoredFile[]>('/api/files');
  }
}

go deeper

for a junior

Know that HttpClient is injected with inject(HttpClient), that provideHttpClient() goes in app.config.ts, and that features like withInterceptors are passed to it as arguments.

for a middle

Explain which defaults apply with no configuration in v22, list the feature functions, and map HttpClientModule to its provider equivalent including withXhr().

for a senior

Decide where each client configuration lives (application versus route injectors) and plan the migration off HttpClientModule without changing the backend by accident.

for a principal

Set a team convention for HTTP configuration in a large app: one root configuration, when route-level clients are allowed, and how interceptors are owned.

## What changed over the last few majors For years the answer to "how do I set up HttpClient?" was "import `HttpClientModule`". That answer is now two steps out of date: 1. **v15 onward**: standalone applications configure the client with `provideHttpClient()` in `bootstrapApplication`'s providers; `HttpClientModule` was deprecated in v18. 2. **v21**: the HTTP services became **provided in root**, so `inject(HttpClient)` works even if nothing configures it. 3. **v22**: the default backend became **`FetchBackend`**, the browser `fetch` API, and `withFetch()` was deprecated because it is now the default. ## What you get with no configuration With no `provideHttpClient()` call at all, an Angular 22.2 application can inject `HttpClient` and it uses: - `FetchBackend` as the `HttpBackend`; - the built-in XSRF interceptor with its default cookie and header names; - no application interceptors. ## When provideHttpClient() is still needed `provideHttpClient(...features)` exists to **configure** the client. Each feature is a function whose result you pass as an argument: | Feature | What it configures | |---|---| | `withInterceptors([fn, ...])` | functional interceptors (`HttpInterceptorFn`), run in array order | | `withInterceptorsFromDi()` | class-based interceptors registered under the `HTTP_INTERCEPTORS` token | | `withXsrfConfiguration({cookieName, headerName})` | custom XSRF cookie and header names | | `withNoXsrfProtection()` | turns the XSRF interceptor off | | `withXhr()` | switches the backend to `HttpXhrBackend` (needed for upload progress) | | `withRequestsMadeViaParent()` | sends requests on to the parent injector's `HttpClient` | | `withJsonpSupport()` | JSONP requests; deprecated since 22.1 | | `withFetch()` | deprecated no-op since v22: fetch is already the default | Two combinations are rejected in development mode with a configuration error: `withXsrfConfiguration()` together with `withNoXsrfProtection()`, and `withRequestsMadeViaParent()` together with `withFetch()` or `withXhr()`. ## Where the call goes `provideHttpClient()` returns **`EnvironmentProviders`**. That type is accepted by: - `ApplicationConfig.providers` in `app.config.ts` (the usual place); - an `NgModule`'s `providers` in module-based apps; - a route's `providers`, which creates a separate client for that part of the app. It is **not** accepted in a component's `providers`, because components do not have an environment injector. ## The NgModule equivalents Codebases that still import the NgModules can map them directly: | NgModule (deprecated) | Provider-function equivalent | |---|---| | `HttpClientModule` | `provideHttpClient(withInterceptorsFromDi(), withXhr())` | | `HttpClientXsrfModule.withOptions(...)` | `withXsrfConfiguration(...)` | | `HttpClientXsrfModule.disable()` | `withNoXsrfProtection()` | | `HttpClientJsonpModule` | `withJsonpSupport()` | Note the `withXhr()` in the first row: an app that still imports `HttpClientModule` keeps the XMLHttpRequest backend even in v22, while a plain `provideHttpClient()` uses fetch. ## How features compose `provideHttpClient()` is variadic: each feature function returns a small descriptor carrying a kind and a list of providers, and `provideHttpClient()` appends those providers after its own defaults. Three consequences follow: - **features are tree-shakable**: an app that never calls `withXhr()` does not need the XHR backend wired in; - **order matters for interceptors**: the built-in XSRF interceptor is registered first, then each feature's interceptors in the order the features are passed; - **contradictions are detectable**: because every feature declares its kind, Angular can reject impossible combinations at startup in development mode. ## Common setup mistakes 1. Passing an options object such as `{interceptors: [...]}`; the API takes feature-function results as separate arguments. 2. Adding `withFetch()` in new v22 code; it is deprecated and changes nothing. 3. Registering class interceptors under `HTTP_INTERCEPTORS` but forgetting `withInterceptorsFromDi()`, so they never run. 4. Putting `provideHttpClient()` in a lazy route without realising it creates a separate client that ignores the root interceptors. ## A practical default for a new app - Leave `HttpClient` unconfigured until you need a feature. - When you add an interceptor, add `provideHttpClient(withInterceptors([...]))` once, in `app.config.ts`. - Prefer functional interceptors; `withInterceptorsFromDi()` is for existing class-based ones, and the source notes that support for DI-provided interceptors may be phased out later.

  • Why can't provideHttpClient() go in a component's providers array?
    It returns `EnvironmentProviders`, which can only be installed in an environment injector: the application's, an `NgModule`'s or a route's. Component providers live in the element injector tree, so the type is rejected. For a separate client in one area of the app, use a lazy route's `providers`.
  • What happens if you pass both withXsrfConfiguration() and withNoXsrfProtection()?
    In development mode `provideHttpClient()` throws a configuration error, because customising XSRF names and disabling XSRF protection contradict each other. Pick one.

saying these in an interview costs you the question

  • HttpClient cannot be injected until HttpClientModule is imported
  • provideHttpClient() can be added to a component's providers to scope it
  • withFetch() must be added in Angular 22 to use the fetch API
  • provideHttpClient() takes a configuration object such as {interceptors: [...]}
  • HttpClientModule and a bare provideHttpClient() configure identical clients in v22