Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more

Angular’s HttpClient is the injectable service an Angular application uses to exchange data with backend servers over HTTP. It exposes methods that match HTTP verbs such as GET, POST, PUT and DELETE, returns response bodies as typed values, reports failures through a consistent error path, and gives you a single place to intercept every outgoing request and incoming response. Angular’s official HTTP Client overview frames the topic as understanding communication with backend services using HTTP, and that is the lens used here.

What HttpClient does in an Angular app

Your components should not build raw network calls themselves. HttpClient handles the transport, while your application code describes what it needs: which endpoint to call, what headers or parameters to send, and what shape of data to expect back. The official overview highlights four capabilities:

  • Typed response values. You declare the expected type in the call, such as get<Product[]>, so the response is typed in your code.
  • Streamlined error handling. Failed requests reach the subscriber’s error callback as an error object, so one pattern covers network and server failures.
  • Request and response interception. Interceptors can inspect or change traffic in one place instead of in each service.
  • Testing utilities. A test backend lets you check requests and supply responses without a real server.

Setting up HttpClient

The setup path depends on your Angular version, and this is the step most likely to be copied wrongly from older tutorials. Angular’s Setting up HttpClient guide says the service is available for injection by default starting with Angular v21. In that version you can inject HttpClient without adding a provider, but you still add provideHttpClient() whenever you need to choose a backend or enable features such as interceptors.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Step-by-step setup for a standalone project

  1. Confirm your Angular version from the project root with ng version, and read the output before copying any configuration.
  2. Open the application configuration file. In a standalone project created by the Angular CLI this is usually src/app/app.config.ts.
  3. Add provideHttpClient() to the providers array. For Angular v21 and later, this step is needed only when you want non-default features.
  4. Inject the service into a data-access service rather than into a component. Import inject from @angular/core and HttpClient from @angular/common/http, then write private http = inject(HttpClient);.
// src/app/app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideHttpClient } from '@angular/common/http';

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

Older projects and deprecated configuration

Older Angular applications may use HttpClientModule. The setup guide marks that module-based configuration as deprecated, so new code should use provideHttpClient() in providers. The same guide also marks JSONP support as deprecated and recommends standard HTTP requests with CORS wherever the backend allows it. If you maintain an older app, check your installed version before you migrate, because the exact upgrade path depends on which Angular release you are on.

Choosing the backend: Fetch or XMLHttpRequest

The current default backend is Fetch. You select the older XMLHttpRequest (XHR) backend with withXhr() inside provideHttpClient(). The table below summarizes what the setup guide says about each choice.

Configuration Backend What the setup guide says
provideHttpClient() with no feature Fetch (default) Fetch is the recommended default for server-side rendering (SSR).
provideHttpClient(withXhr()) XMLHttpRequest Not for SSR. The guide cites unsafe redirect handling and a denial-of-service risk from redirect loops. Server-side XHR support is deprecated and intended for removal in Angular 23.

The guide includes the subsection heading “Do not use withXhr in server-side rendering (SSR) environments.” If your app renders on the server, remove withXhr() and use the default backend. For client-only apps, use XHR only if you have a specific environment requirement, since the default is the documented starting point.

How requests behave: the Observable model

Every request method on HttpClient returns an Observable. Nothing is sent when you call the method. The request starts when you subscribe, and each subscription issues a new backend request. That means a service method that is subscribed twice will call the server twice, which is a common source of duplicate requests.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
const products$ = this.http.get<Product[]>('/api/products');

products$.subscribe();   // sends GET /api/products
products$.subscribe();   // sends a second GET /api/products

By default the Observable emits only the response body. If you need the status code or headers, pass observe: 'response' and read the full response object:

this.http
  .get<Product[]>('/api/products', { observe: 'response' })
  .subscribe(response => {
    console.log(response.status);
    console.log(response.headers.get('Content-Type'));
    console.log(response.body);
  });

Options such as query parameters, headers and response type are passed in the same options object, so the call signature stays consistent across GET, POST and the other verbs.

Organizing data access in reusable services

Angular recommends putting request logic in injectable services that encapsulate data access. Components then ask a service for data and do not need to know URLs, headers or backend details. This keeps endpoint changes in one file and makes the service easy to test.

import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

export interface Product {
  id: number;
  name: string;
  price: number;
}

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

  getProducts(): Observable<Product[]> {
    return this.http.get<Product[]>('/api/products');
  }
}

When a component consumes the Observable, let Angular manage the subscription. Two patterns are recommended in the guide: the async pipe in templates, and toSignal when you want a signal inside the component class. With toSignal, the subscription is tied to the component’s lifetime, so you do not need to unsubscribe manually.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
import { Component, inject } from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop';
import { ProductService } from './product.service';

@Component({ /* selector, template, etc. */ })
export class ProductListComponent {
  private productService = inject(ProductService);
  products = toSignal(this.productService.getProducts(), { initialValue: [] });
}

Interceptors

Interceptors are middleware that sit between your code and the backend. Angular recommends functional interceptors because their behavior is more predictable, particularly in larger configurations. Typical uses include adding authentication headers, retrying failed requests, caching responses, logging, measuring timing, driving loading indicators, batching requests and enforcing timeouts.

Functional interceptors with withInterceptors

A functional interceptor is a function of type HttpInterceptorFn. You register one or more with withInterceptors([...]), and they run in the order listed. The example below adds a bearer token; AuthService is an application service you write yourself.

// src/app/auth.interceptor.ts
import { HttpInterceptorFn } from '@angular/common/http';
import { inject } from '@angular/core';
import { AuthService } from './auth.service';

export const authInterceptor: HttpInterceptorFn = (req, next) => {
  const token = inject(AuthService).token();
  if (!token) {
    return next(req);
  }
  return next(req.clone({ setHeaders: { Authorization: `Bearer ${token}` } }));
};

// src/app/app.config.ts
import { provideHttpClient, withInterceptors } from '@angular/common/http';

providers: [
  provideHttpClient(withInterceptors([authInterceptor])),
]

Class-based (DI) interceptors

Class-based interceptors are still supported, but they need two pieces: withInterceptorsFromDi() inside provideHttpClient(), and a class registered under the HTTP_INTERCEPTORS multi-provider. Angular warns that execution order can be hard to predict in extensive hierarchical dependency-injection setups. For new code, prefer functional interceptors unless you are maintaining an existing class-based chain.

providers: [
  provideHttpClient(withInterceptorsFromDi()),
  { provide: HTTP_INTERCEPTORS, useClass: LoggingInterceptor, multi: true },
]
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Testing HTTP calls

The testing backend lets your tests run application code, check which requests were made, and return controlled responses, all without contacting a real server. Use provideHttpClientTesting() to install it and inject HttpTestingController to control it.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  1. Configure the testing module with provideHttpClient() and provideHttpClientTesting(). If your test depends on interceptors or other HttpClient features, pass them to provideHttpClient(...) and place that call before provideHttpClientTesting(), because the testing provider overwrites parts of the normal setup.
  2. Inject your service and HttpTestingController.
  3. Call the service method and subscribe. Nothing is sent to a server.
  4. Use expectOne() to capture the request, then flush() to supply the response.
  5. In afterEach, call verify() so the test fails if an unexpected request was made.
import { TestBed } from '@angular/core/testing';
import { provideHttpClient } from '@angular/common/http';
import { provideHttpClientTesting, HttpTestingController } from '@angular/common/http/testing';
import { ProductService } from './product.service';

describe('ProductService', () => {
  let service: ProductService;
  let httpMock: HttpTestingController;

  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [provideHttpClient(), provideHttpClientTesting()],
    });
    service = TestBed.inject(ProductService);
    httpMock = TestBed.inject(HttpTestingController);
  });

  afterEach(() => httpMock.verify());

  it('loads products from the API', () => {
    let result: unknown;
    service.getProducts().subscribe(products => (result = products));

    const req = httpMock.expectOne('/api/products');
    expect(req.request.method).toBe('GET');
    req.flush([{ id: 1, name: 'Lamp', price: 20 }]);

    expect(result).toEqual([{ id: 1, name: 'Lamp', price: 20 }]);
  });
});

Multi-injector and child-injector setups

In applications with child injectors, a child HttpClient normally overrides the configuration inherited from its parent. If the child should reuse the parent’s configuration, including its interceptors, enable withRequestsMadeViaParent(). Prefer provider-based configuration in these setups, since it is the approach the setup guide describes.

Where the official guidance may change

The points above reflect Angular’s setup guide and the HTTP Client overview as checked in October 2026. Version-specific items, including the Angular v21 default injection behavior and the planned removal of server-side XHR in Angular 23, can change in later releases. Before you copy any configuration, confirm it against the Angular version installed in your project.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.