skip to content

Since Angular 22, which backend does HttpClient use by default, and why would a file-upload feature need withXhr()?

level: middleimportance: should knowfreq 48%

answer

  1. the browser's newer request API
  2. withFetch became a no-op
  3. fetch cannot report upload progress
  4. reportUploadProgress replaces reportProgress
  5. keep XHR off the server

basics

~20 s

Since v22 HttpClient uses FetchBackend, built on the fetch API, and withFetch() is deprecated. Fetch cannot report upload progress, so an upload bar with reportUploadProgress needs provideHttpClient(withXhr()), which switches to HttpXhrBackend; on FetchBackend that option fails with NG02824.

solid answer

~40 s

In Angular 22 the default `HttpBackend` is **`FetchBackend`**, which sends requests with the browser's `fetch` API; `withFetch()` is now a deprecated no-op. Fetch has no upload-progress events, so a file-upload screen that wants a percentage bar must opt back into XMLHttpRequest with `provideHttpClient(withXhr())`, which makes `HttpXhrBackend` the backend. The request then uses `observe: 'events'` and `reportUploadProgress: true`, and the stream carries `HttpEventType.UploadProgress` events with `loaded` and `total`. On `FetchBackend`, a request with `reportUploadProgress: true` fails with runtime error NG02824. `reportProgress` was deprecated in v22 in favour of `reportUploadProgress` and `reportDownloadProgress`; download progress works on both backends. Do not use `withXhr()` in server-side rendering: server XHR is deprecated and slated for removal in v23.

code

ts · 28 lines
ts
// app.config.ts: switch the backend so uploads can report progress (browser-only app)
import {ApplicationConfig} from '@angular/core';
import {provideHttpClient, withXhr} from '@angular/common/http';

export const appConfig: ApplicationConfig = {
  providers: [provideHttpClient(withXhr())],
};

// upload.service.ts
import {HttpClient, HttpEventType, HttpUploadProgressEvent} from '@angular/common/http';
import {inject, Injectable} from '@angular/core';
import {filter, map} from 'rxjs';

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

  upload(file: File) {
    const body = new FormData();
    body.append('file', file);
    return this.http
      .post('/api/files', body, {observe: 'events', reportUploadProgress: true})
      .pipe(
        filter((e): e is HttpUploadProgressEvent => e.type === HttpEventType.UploadProgress),
        map((e) => (e.total ? Math.round((100 * e.loaded) / e.total) : 0)),
      );
  }
}

go deeper

for a junior

Know that HttpClient uses fetch by default in Angular 22 and that a progress bar for uploads needs provideHttpClient(withXhr()).

for a middle

Explain the backend abstraction, why fetch cannot report upload progress, and the reportUploadProgress, observe: 'events' and UploadProgress event mechanics.

for a senior

Choose the backend for an SSR application with an upload screen, account for the v23 removal of server XHR, and scope XHR to where it is needed.

for a principal

Decide whether exact upload progress justifies a second backend in the app, weighing product value against SSR safety and configuration complexity.

## Backends in HttpClient `HttpClient` never talks to the network itself. It passes each request through the interceptor chain to an **`HttpBackend`**, the object that actually performs the request. `@angular/common/http` ships two browser backends: | Backend | Built on | Selected by | |---|---|---| | `FetchBackend` | the `fetch` API | default since v22 (was opt-in through `withFetch()`) | | `HttpXhrBackend` | `XMLHttpRequest` | `provideHttpClient(withXhr())`, or the deprecated `HttpClientModule` | The switch in v22 made the modern API the default. `withFetch()` still compiles but is deprecated and does nothing new, so it can simply be removed. ## What the switch costs: upload progress `fetch` can stream a **response** body, so download progress still works on `FetchBackend`. It has no way to observe how much of a **request** body has been sent, so it cannot produce upload-progress events. Angular makes that explicit: - a request with `reportUploadProgress: true` on `FetchBackend` fails with runtime error **NG02824**, whose message tells you to add `withXhr()`; - on `HttpXhrBackend`, the same option makes the event stream include `HttpEventType.UploadProgress` events carrying `loaded` and, when known, `total`. In v22 the old catch-all option `reportProgress` was deprecated in favour of the two specific ones, `reportUploadProgress` and `reportDownloadProgress`. ## Configuring the upload feature For a browser-only application with a file-upload screen: 1. Add `withXhr()` to `provideHttpClient()` in `app.config.ts`. 2. Send the file as `FormData` with `observe: 'events'` and `reportUploadProgress: true`. 3. Filter the event stream for `UploadProgress` events and compute `loaded / total`. 4. Handle the final `HttpEventType.Response` event (or completion) to know the upload finished. ## Reading the event stream With `observe: 'events'`, the request Observable emits every stage of the exchange, identified by `HttpEventType`: | Event | When it arrives | |---|---| | `Sent` | the request has been dispatched | | `UploadProgress` | request-body bytes sent (XHR backend with `reportUploadProgress`) | | `ResponseHeader` | status and headers received | | `DownloadProgress` | response-body bytes received (with `reportDownloadProgress`) | | `Response` | the complete `HttpResponse` | An upload component usually maps `UploadProgress` to a percentage and treats `Response` as completion; the other events can be ignored. ## Server-side rendering changes the answer With SSR the same `HttpClient` code also runs on the server. There: - XHR support is **deprecated** and intended to be removed in **Angular 23**, because the underlying `xhr2` library mishandles redirects (it can forward `Authorization` headers across origins and can loop); - in development mode Angular warns with **NG02801** when a server-rendered app is not using `FetchBackend`. So an SSR application should keep the fetch default. For its upload screen, the choices are an indeterminate spinner instead of a percentage, or a route-level client configured with `withXhr()` for the browser-only upload area (see the injector-hierarchy caveats of route-level clients). ## Upgraded apps keep XHR until you remove it `ng update` to v22 runs a migration that inserts `withXhr()` into every `provideHttpClient()` call that has neither `withFetch()` nor `withXhr()`. An upgraded app therefore keeps its previous backend, and moving to fetch is a deliberate edit: delete `withXhr()` where upload progress is not needed. New v22 projects start on fetch. Apps that already used `withFetch()` are left alone and can drop that call. ## Why fetch is the default anyway - `fetch` is available in environments where `XMLHttpRequest` is not, including server runtimes; - it is the backend Angular recommends for server-side rendering; - most requests never need upload progress, so the trade-off favours fetch for the whole app and XHR only where a progress bar is a product requirement. ## Checklist for the interview - default backend in v22: `FetchBackend`; `withFetch()` deprecated; - upload progress needs `withXhr()` plus `reportUploadProgress`; - NG02824 if you ask fetch for upload progress; NG02801 warns about non-fetch backends under SSR; - no `withXhr()` in SSR builds going forward.

  • The upload bar stays at 0% and only the final response arrives. What do you check first?
    That the request sets both `observe: 'events'` and `reportUploadProgress: true`; without them `HttpClient` emits only the final response. With the flag set, the app must also be on `withXhr()`, since `FetchBackend` would fail the request with NG02824. Finally, guard the maths: `total` can be undefined when the size is unknown.
  • Does download progress also require withXhr()?
    No. `FetchBackend` can read the response as a stream, so `reportDownloadProgress: true` with `observe: 'events'` produces `DownloadProgress` events on the default backend. Only upload progress needs XMLHttpRequest.

Fetch is a courier who tells you when the reply arrives and can read it to you page by page, but never phones back while carrying your parcel out. XMLHttpRequest is the older courier who reports how much of your parcel has left the building. For a progress bar on outgoing files you still hire the older courier.

saying these in an interview costs you the question

  • HttpClient still uses XMLHttpRequest by default in Angular 22
  • withFetch() is required to use fetch in Angular 22
  • FetchBackend can report upload progress if reportProgress is set
  • Download progress also needs withXhr()
  • withXhr() is the recommended backend for server-side rendering