Online

Making HTTP requests

HttpClient has methods corresponding to the different HTTP verbs used to make requests, both to load data and to apply mutations on the server. Each method returns an RxJS Observable which, when subscribed, sends the request and then emits the results when the server responds.

Through an options object passed to the request method, various properties of the request and the returned response type can be adjusted.

Fetching JSON data

Fetching data from a backend often requires making a GET request using the HttpClient.get() method. This method takes two arguments: the string endpoint URL from which to fetch, and an optional options object to configure the request.

For example, to fetch configuration data from a hypothetical API using the HttpClient.get() method:

ts
http.get<Config>('/api/config').subscribe((config) => {
  // process the configuration.
});

Note the generic type argument which specifies that the data returned by the server will be of type Config. This argument is optional, and if you omit it, the returned data will have type Object.

Fetching other types of data

By default, HttpClient assumes that servers will return JSON data. When interacting with a non-JSON API, you can tell HttpClient what response type to expect when making the request. This is done with the responseType option.

responseType valueReturned response type
'json' (default)JSON data of the given generic type
'text'string data
'arraybuffer'ArrayBuffer containing the raw response bytes
'blob'Blob instance

For example, you can ask HttpClient to download the raw bytes of a .jpeg image into an ArrayBuffer:

ts
http.get('/images/dog.jpg', {responseType: 'arraybuffer'}).subscribe((buffer) => {
  console.log('The image is ' + buffer.byteLength + ' bytes large');
});

Mutating server state

Server APIs that perform mutations often require making POST requests with a request body specifying the new state or the change to be made.

The HttpClient.post() method behaves similarly to get(), and accepts an additional body argument before its options:

ts
http.post<Config>('/api/config', newConfig).subscribe((config) => {
  console.log('Updated config:', config);
});

Many different types of values can be provided as the request's body, and HttpClient will serialize them accordingly:

body typeSerialized as
stringPlain text
number, boolean, array, or plain objectJSON
ArrayBufferraw data from the buffer
Blobraw data with the Blob's content type
FormDatamultipart/form-data encoded data
HttpParams or URLSearchParamsapplication/x-www-form-urlencoded formatted string

Setting URL parameters

Specify request parameters that should be included in the request URL using the params option.

Passing an object literal is the simplest way of configuring URL parameters:

ts
http
  .get('/api/config', {
    params: {filter: 'all'},
  })
  .subscribe((config) => {
    // ...
  });

Alternatively, pass an instance of HttpParams if you need more control over the construction or serialization of the parameters.

ts
const baseParams = new HttpParams().set('filter', 'all');

http
  .get('/api/config', {
    params: baseParams.set('details', 'enabled'),
  })
  .subscribe((config) => {
    // ...
  });

You can instantiate HttpParams with a custom HttpParameterCodec that determines how HttpClient will encode the parameters into the URL.

Custom parameter encoding

By default, HttpParams uses the built-in HttpUrlEncodingCodec to encode and decode parameter keys and values.

You can provide your own implementation of HttpParameterCodec to customize how encoding and decoding are applied.

ts
import {HttpClient, HttpParams, HttpParameterCodec} from '@angular/common/http';
import {inject} from '@angular/core';

export class CustomHttpParamEncoder implements HttpParameterCodec {
  encodeKey(key: string): string {
    return encodeURIComponent(key);
  }

  encodeValue(value: string): string {
    return encodeURIComponent(value);
  }

  decodeKey(key: string): string {
    return decodeURIComponent(key);
  }

  decodeValue(value: string): string {
    return decodeURIComponent(value);
  }
}

export class ApiService {
  private http = inject(HttpClient);

  search() {
    const params = new HttpParams({
      encoder: new CustomHttpParamEncoder(),
    })
      .set('email', 'dev+alerts@example.com')
      .set('q', 'a & b? c/d = e');

    return this.http.get('/api/items', {params});
  }
}

Setting request headers

Specify request headers that should be included in the request using the headers option.

Passing an object literal is the simplest way of configuring request headers:

ts
http
  .get('/api/config', {
    headers: {
      'X-Debug-Level': 'verbose',
    },
  })
  .subscribe((config) => {
    // ...
  });

Alternatively, pass an instance of HttpHeaders if you need more control over the construction of headers.

ts
const baseHeaders = new HttpHeaders().set('X-Debug-Level', 'minimal');

http
  .get<Config>('/api/config', {
    headers: baseHeaders.set('X-Debug-Level', 'verbose'),
  })
  .subscribe((config) => {
    // ...
  });

Interacting with the server response events

For convenience, HttpClient by default returns an Observable of the data returned by the server (the response body). Occasionally it's desirable to examine the actual response, for example to retrieve specific response headers.

To access the entire response, set the observe option to 'response':

ts
http.get<Config>('/api/config', {observe: 'response'}).subscribe((res) => {
  console.log('Response status:', res.status);
  console.log('Body:', res.body);
});

Receiving raw progress events

In addition to the response body or response object, HttpClient can also return a stream of raw events corresponding to specific moments in the request lifecycle. These events include when the request is sent, when the response header is returned, and when the body is complete. These events can also include progress events that report upload and download status for large request or response bodies.

Progress events are disabled by default (as they have a performance cost) but can be enabled with the reportProgress option.

To observe the event stream, set the observe option to 'events':

ts
http
  .post('/api/upload', myData, {
    reportProgress: true,
    observe: 'events',
  })
  .subscribe((event) => {
    switch (event.type) {
      case HttpEventType.UploadProgress:
        console.log('Uploaded ' + event.loaded + ' out of ' + event.total + ' bytes');
        break;
      case HttpEventType.Response:
        console.log('Finished uploading!');
        break;
    }
  });

Each HttpEvent reported in the event stream has a type which distinguishes what the event represents:

type valueEvent meaning
HttpEventType.SentThe request has been dispatched to the server
HttpEventType.UploadProgressAn HttpUploadProgressEvent reporting progress on uploading the request body
HttpEventType.ResponseHeaderThe head of the response has been received, including status and headers
HttpEventType.DownloadProgressAn HttpDownloadProgressEvent reporting progress on downloading the response body
HttpEventType.ResponseThe entire response has been received, including the response body
HttpEventType.UserA custom event from an HTTP interceptor.

Handling request failure

There are three ways an HTTP request can fail:

  • A network or connection error can prevent the request from reaching the backend server.
  • A request didn't respond in time when the timeout option was set.
  • The backend can receive the request but fail to process it, and return an error response.

HttpClient captures all of the above kinds of errors in an HttpErrorResponse which it returns through the Observable's error channel. Network and timeout errors have a status code of 0 and an error which is an instance of ProgressEvent. Backend errors have the failing status code returned by the backend, and the error response as the error. Inspect the response to identify the error's cause and the appropriate action to handle the error.

The RxJS library offers several operators which can be useful for error handling.

You can use the catchError operator to transform an error response into a value for the UI. This value can tell the UI to display an error page or value, and capture the error's cause if necessary.

Sometimes transient errors such as network interruptions can cause a request to fail unexpectedly, and simply retrying the request will allow it to succeed. RxJS provides several retry operators which automatically re-subscribe to a failed Observable under certain conditions. For example, the retry() operator will automatically attempt to re-subscribe a specified number of times.

Timeouts

To set a timeout for a request, you can set the timeout option to a number of milliseconds along with other request options. If the backend request does not complete within the specified time, the request will be aborted and an error will be emitted.

ts
http
  .get('/api/config', {
    timeout: 3000,
  })
  .subscribe({
    next: (config) => {
      console.log('Config fetched successfully:', config);
    },
    error: (err) => {
      // If the request times out, an error will have been emitted.
    },
  });

Advanced fetch options

Angular's HttpClient supports advanced fetch API options that can improve performance and user experience. These options are available when using the fetch backend, which is the default.

Fetch options

The following options provide fine-grained control over request behavior when using the fetch backend.

Keep-alive connections

The keepalive option allows a request to outlive the page that initiated it. This is particularly useful for analytics or logging requests that need to complete even if the user navigates away from the page.

ts
http
  .post('/api/analytics', analyticsData, {
    keepalive: true,
  })
  .subscribe();

HTTP caching control

The cache option controls how the request interacts with the browser's HTTP cache, which can significantly improve performance for repeated requests.

ts
//  Use cached response regardless of freshness
http
  .get('/api/config', {
    cache: 'force-cache',
  })
  .subscribe((config) => {
    // ...
  });

// Always fetch from network, bypass cache
http
  .get('/api/live-data', {
    cache: 'no-cache',
  })
  .subscribe((data) => {
    // ...
  });

// Use cached response only, fail if not in cache
http
  .get('/api/static-data', {
    cache: 'only-if-cached',
  })
  .subscribe((data) => {
    // ...
  });

Request priority for Core Web Vitals

The priority option allows you to indicate the relative importance of a request, helping browsers optimize resource loading for better Core Web Vitals scores.

ts
// High priority for critical resources
http
  .get('/api/user-profile', {
    priority: 'high',
  })
  .subscribe((profile) => {
    // ...
  });

// Low priority for non-critical resources
http
  .get('/api/recommendations', {
    priority: 'low',
  })
  .subscribe((recommendations) => {
    // ...
  });

// Auto priority (default) lets the browser decide
http
  .get('/api/settings', {
    priority: 'auto',
  })
  .subscribe((settings) => {
    // ...
  });

Available priority values:

  • 'high': High priority, loaded early (e.g., critical user data, above-the-fold content)
  • 'low': Low priority, loaded when resources are available (e.g., analytics, prefetch data)
  • 'auto': Browser determines priority based on request context (default)

Request mode

The mode option controls how the request handles cross-origin requests and determines the response type.

ts
// Same-origin requests only
http
  .get('/api/local-data', {
    mode: 'same-origin',
  })
  .subscribe((data) => {
    // ...
  });

// CORS-enabled cross-origin requests
http
  .get('https://api.external.com/data', {
    mode: 'cors',
  })
  .subscribe((data) => {
    // ...
  });

// No-CORS mode for simple cross-origin requests
http
  .get('https://external-api.com/public-data', {
    mode: 'no-cors',
  })
  .subscribe((data) => {
    // ...
  });

Available mode values:

  • 'same-origin': Only allow same-origin requests, fail for cross-origin requests
  • 'cors': Allow cross-origin requests with CORS (default)
  • 'no-cors': Allow simple cross-origin requests without CORS, response is opaque

Redirect handling

The redirect option specifies how to handle redirect responses from the server.

ts
// Follow redirects automatically (default behavior)
http
  .get('/api/resource', {
    redirect: 'follow',
  })
  .subscribe((data) => {
    // ...
  });

// Prevent automatic redirects
http
  .get('/api/resource', {
    redirect: 'manual',
  })
  .subscribe((response) => {
    // Handle redirect manually
  });

// Treat redirects as errors
http
  .get('/api/resource', {
    redirect: 'error',
  })
  .subscribe({
    next: (data) => {
      // Success response
    },
    error: (err) => {
      // Redirect responses will trigger this error handler
    },
  });

Available redirect values:

  • 'follow': Automatically follow redirects (default)
  • 'error': Treat redirects as errors
  • 'manual': Don't follow redirects automatically, return redirect response

Credentials handling

The credentials option controls whether cookies, authorization headers, and other credentials are sent with cross-origin requests. This is particularly important for authentication scenarios.

ts
// Include credentials for cross-origin requests
http
  .get('https://api.example.com/protected-data', {
    credentials: 'include',
  })
  .subscribe((data) => {
    // ...
  });

// Never send credentials (default for cross-origin)
http
  .get('https://api.example.com/public-data', {
    credentials: 'omit',
  })
  .subscribe((data) => {
    // ...
  });

// Send credentials only for same-origin requests
http
  .get('/api/user-data', {
    credentials: 'same-origin',
  })
  .subscribe((data) => {
    // ...
  });

// withCredentials overrides credentials setting
http
  .get('https://api.example.com/data', {
    credentials: 'omit', // This will be ignored
    withCredentials: true, // This forces credentials: 'include'
  })
  .subscribe((data) => {
    // Request will include credentials despite credentials: 'omit'
  });

// Legacy approach (still supported)
http
  .get('https://api.example.com/data', {
    withCredentials: true,
  })
  .subscribe((data) => {
    // Equivalent to credentials: 'include'
  });

Available credentials values:

  • 'omit': Never send credentials
  • 'same-origin': Send credentials only for same-origin requests (default)
  • 'include': Always send credentials, even for cross-origin requests

Referrer

The referrer option allows you to control what referrer information is sent with the request. This is important for privacy and security considerations.

ts
// Send a specific referrer URL
http
  .get('/api/data', {
    referrer: 'https://example.com/page',
  })
  .subscribe((data) => {
    // ...
  });

// Use the current page as referrer (default behavior)
http
  .get('/api/analytics', {
    referrer: 'about:client',
  })
  .subscribe((data) => {
    // ...
  });

The referrer option accepts:

  • A valid URL string: Sets the specific referrer URL to send
  • An empty string '': Sends no referrer information
  • 'about:client': Uses the default referrer (current page URL)

Referrer policy

The referrerPolicy option controls how much referrer information—the URL of the page making the request—is sent along with an HTTP request. This setting affects both privacy and analytics, allowing you to balance data visibility with security considerations.

ts
// Send no referrer information regardless of the current page
http
  .get('/api/data', {
    referrerPolicy: 'no-referrer',
  })
  .subscribe();

// Send origin only (e.g. https://example.com)
http
  .get('/api/analytics', {
    referrerPolicy: 'origin',
  })
  .subscribe();

The referrerPolicy option accepts:

  • 'no-referrer' Never send the Referer header.
  • 'no-referrer-when-downgrade' Sends the referrer for same-origin and secure (HTTPS→HTTPS) requests, but omits it when navigating from a secure to a less secure origin (HTTPS→HTTP).
  • 'origin' Sends only the origin (scheme, host, port) of the referrer, omitting path and query information.
  • 'origin-when-cross-origin' Sends the full URL for same-origin requests, but only the origin for cross-origin requests.
  • 'same-origin' Sends the full URL for same-origin requests and no referrer for cross-origin requests.
  • 'strict-origin' Sends only the origin, and only if the protocol security level is not downgraded (e.g., HTTPS→HTTPS). Omits the referrer on downgrade.
  • 'strict-origin-when-cross-origin' Default browser behavior. Sends the full URL for same-origin requests, the origin for cross-origin requests when not downgraded, and omits the referrer on downgrade.
  • 'unsafe-url' Always sends the full URL (including path and query). This can expose sensitive data and should be used with caution.

Integrity

The integrity option allows you to verify that the response hasn't been tampered with by providing a cryptographic hash of the expected content. This is particularly useful for loading scripts or other resources from CDNs.

ts
// Verify response integrity with SHA-256 hash
http
  .get('/api/script.js', {
    integrity: 'sha256-ABC123...',
    responseType: 'text',
  })
  .subscribe((script) => {
    // Script content is verified against the hash
  });

HTTP Observables

Each request method on HttpClient constructs and returns an Observable of the requested response type. Understanding how these Observables work is important when using HttpClient.

HttpClient produces what RxJS calls "cold" Observables, meaning that no actual request happens until the Observable is subscribed. Only then is the request actually dispatched to the server. Subscribing to the same Observable multiple times will trigger multiple backend requests. Each subscription is independent.

Once subscribed, unsubscribing will abort the in-progress request. This is very useful if the Observable is subscribed via the async pipe, as it will automatically cancel the request if the user navigates away from the current page. Additionally, if you use the Observable with an RxJS combinator like switchMap, this cancellation will clean up any stale requests.

Once the response returns, Observables from HttpClient usually complete (although interceptors can influence this).

Because of the automatic completion, there is usually no risk of memory leaks if HttpClient subscriptions are not cleaned up. However, as with any async operation, we strongly recommend that you clean up subscriptions when the component using them is destroyed, as the subscription callback may otherwise run and encounter errors when it attempts to interact with the destroyed component.

Best practices

While HttpClient can be injected and used directly from components, generally we recommend you create reusable, injectable services which isolate and encapsulate data access logic. For example, this UserService encapsulates the logic to request data for a user by their id:

ts
@Service()
export class UserService {
  private http = inject(HttpClient);

  getUser(id: string): Observable<User> {
    return this.http.get<User>(`/api/user/${id}`);
  }
}

Within a component, you can combine @if with the async pipe to render the UI for the data only after it's finished loading:

ts
import {AsyncPipe} from '@angular/common';

@Component({
  imports: [AsyncPipe],
  template: `
    @if (user$ | async; as user) {
      <p>Name: {{ user.name }}</p>
      <p>Biography: {{ user.biography }}</p>
    }
  `,
})
export class UserProfile {
  userId = input.required<string>();
  user$!: Observable<User>;

  private userService = inject(UserService);

  constructor(): void {
    effect(() => {
      this.user$ = this.userService.getUser(this.userId());
    });
  }
}