Angular HTTP Client: A Practical Starting Point
This guide offers a hands-on introduction to Angular's HTTP Client module. While the focus is on the newer @angular/common/http package, much of the advice here also applies to the earlier @angular/http module. We'll walk through the most frequent patterns you'll encounter when building applications.
What We'll Cover
- An overview of the new HTTP Client module
- A simple HTTP GET request example
- How the new API improves type safety
- Working with URL parameters and headers (immutable APIs)
- Performing PUT, PATCH, POST, and DELETE operations
- REST API guidelines for using different HTTP methods effectively
- Leveraging generics for more type-safe code
- Preventing duplicate HTTP requests
- Running requests in parallel and combining their results
- Chaining requests in sequence and using the first result to build the second
- Getting results from two sequential requests
- Handling HTTP errors gracefully
- Using HTTP interceptors for cross-cutting concerns
- Tracking request progress events
A runnable version of the code from this post is available in this repository.
Introducing the New HTTP Module
Across its various versions, Angular's HTTP module has consistently relied on an RxJS Observable-based API. This design means that every call to the HTTP module returns an observable, which must be subscribed to in order for the request to execute.
Here are some important characteristics of the observables returned by the HTTP module:
- Without a subscription, these observables do nothing; the request won't be made.
- Each subscription to the observable will trigger a separate HTTP request. For a deeper dive, see this post on common RxJS pitfalls.
- These are single-value streams: a successful HTTP request causes the observable to emit exactly one value and then complete.
- If the request encounters an error, the observable will emit an error notification, a topic we'll explore shortly.
With these fundamentals in mind, let's explore common tasks you'll undertake with the HTTP library.
Setting Up the HTTP Module
To begin, you'll need to import HttpClientModule into your root module. This is the first step to make the HTTP functionality available throughout your application.
The Sample REST API
For our examples, we'll query a Firebase database using its built-in REST interface. While tools like AngularFire are more common, Firebase does provide direct REST support for such tasks.
This is the JSON data structure we'll be working with, which uses key-value pairs instead of arrays. The unique-looking strings are Firebase's identifiers, which possess useful properties as explained in this post.

A Basic HTTP GET Request
Let's look at a small component that fetches and displays data from our Firebase database using an HTTP GET request. This component shows a list of courses, and here’s how it works step by step:
- We inject the new
HttpClientservice through the component's constructor. - We invoke the
get()method, which returns an RxJS Observable. - By default, the client assumes a JSON API, so it automatically parses the response body into a JavaScript object.
- APIs are typically designed to return a single object rather than a top-level array to mitigate a security issue known as JSON Hijacking.
- Consequently, we must transform the returned object into an array by extracting its values.
- We use the lodash
valuesutility function for this conversion, mapping the Firebase response into an array. - The resulting observable is stored in a variable named
courses$, which is then consumed by the component's template. - The
asyncpipe is responsible for subscribing to this observable. This implicit subscription is what triggers the HTTP request.
As a result, the titles of all courses from the database are displayed in a bulleted list on the screen.
Enhancing Type Safety
Notice that we pass a generic parameter to the get() call. By specifying Course[], we're telling TypeScript that the observable will emit an array of Course objects. If we omit this type parameter, the call will return an Observable<Object>, which is less informative for our code.
Sending HTTP Request Parameters
The HTTP GET method also accepts parameters which are appended to the URL. Consider this example URL with pagination controls:
https://angular-http-guide.firebaseio.com/courses.json?orderBy="$key"&limitToFirst=1
This request retrieves the same set of courses, but orders them by their $key property. The first parameter, orderBy, specifies the sorting key, while limitToFirst controls the number of results.
Here is how we can construct and send this request using the Angular HTTP Client:
Note that the HttpParams object is built by chaining calls to the set() method. This is required because HttpParams is immutable; its methods don't alter the current object in place.
Instead, each call to set() returns a new HttpParams instance containing the updated values. This means that the following approach will not work as intended:
If you attempt to populate your parameters this way, you'll end up with an empty HttpParams object, and the chained set() calls will have no effect.
The fromString Syntax for Parameters
If you have a pre-built query string, you can use the fromString syntax to create your HttpParams object directly from it.
The More General request() Method
All the GET examples we've seen can also be written using the more general request() API, which also supports other HTTP methods like PUT, POST, and DELETE. Here is how the same GET request would look using this generic method:
This syntax is more flexible because it takes the HTTP method as its first argument, for example, 'GET'.
Setting HTTP Headers
In addition to the headers the browser sets automatically, you can add custom ones using the HttpHeaders class. This is particularly useful for authentication tokens or content-type declarations.
As we saw with HttpParams, the HttpHeaders class also has an immutable API. We pass a configuration object as the second argument to get(), which contains a headers property. Since our const is named headers, we can use the object shorthand notation for a more concise syntax.
Using HTTP PUT
Beyond reading data, the Angular HTTP Client supports all the standard modification methods, such as PUT. The semantic of PUT is to fully replace the target resource. For instance, you'd use PUT to overwrite a course object with an entirely new version.
Let's say this is a method within a component, triggered by a button click. The console output would look like this:
PUT call successful value returned in body
{courseListIcon: "https://angular-academy.s3.amazonaws.com/main-logo/main-page-logo-small-hat.png", description: "Angular Tutorial For Beginners TEST", iconUrl: "https://angular-academy.s3.amazonaws.com/thumbnails/angular2-for-beginners.jpg", longDescription: "...", url: "new-value-for-url"}
The PUT observable is now completed.
As shown, the PUT call overwrites the entire course content with the new object, even if you only intend to change a couple of fields. Additionally, the response body will contain the full new version of the course, which might be a large amount of data.
Patching Data with HTTP PATCH
More often than not, you'll want to update just a single property rather than replace the entire resource. This is the primary use case for the HTTP PATCH method. Here, we'll update only the description of a course:
The console output for this PATCH operation is as follows:
PATCH call successful value returned in body
{description: "Angular Tutorial For Beginners PATCH TEST"}
The PATCH observable is now completed.
The PATCH response will contain only the updated values you sent. This becomes useful when the server itself modifies the patched values further, for instance, via a database trigger or a Firebase rule.
Deleting with HTTP DELETE
Another common operation is performing a logical delete of data. Depending on the backend, this could either permanently erase the entry or simply mark it as deleted. Here's how to delete a course:
The console output from this action is:
DELETE call successful value returned in body null
The DELETE observable is now completed.
With Firebase, this action permanently removes the object. Other REST APIs, however, might only perform a soft delete, keeping the data but flagging it.
The Wildcard: HTTP POST
If your operation doesn't neatly fit into GET, PUT, PATCH, or DELETE, you can opt for the HTTP POST method. This is typically used for adding new data, but it has many other applications. Here's an example of how to create a new course.
And these are the console results when the POST request runs:
POST call successful value returned in body {name: "-KolPZIn25aSYCNJfHK5"}
The POST observable is now completed.
When using POST to create new data, it's conventional for the server to return a unique identifier for the newly created resource. This allows the client to reference it later if needed.
Preventing Duplicate HTTP Requests
Depending on how you structure your code, you might accidentally trigger multiple HTTP requests. This is inherent to how HTTP observables work and can be surprising at first. You might want to create an observable and simultaneously subscribe to it locally (e.g., for logging) right where it's defined.
In this scenario, we create an HTTP observable and subscribe to it locally. We also assign it to the courses$ member variable. Later, the template's async pipe subscribes again. This results in two HTTP requests—one for each subscription—even though we only intended to fetch the data once.
Introducing the shareReplay Operator
There are several ways to prevent duplicate requests. A recent RxJS operator, shareReplay, was designed for this exact purpose. As Ben Lesh, the operator's author, notes:
This makes
shareReplayideal for handling things like caching AJAX results, as it's retryable
By applying this operator, we ensure that the HTTP request is shared among all subscribers, preventing accidental duplicate calls.
This concludes our coverage of primary read and write operations for custom REST APIs. Let's now look at some other common scenarios and additional features of the new client.
Making Parallel Requests and Combining Results
To execute multiple HTTP requests in parallel, we can use the RxJS forkJoin operator. This operator takes several observables and emits a single value only after all of them have emitted. In this example, we combine two GET requests.
The resulting observable waits until both GET requests complete, and then emits an array containing the individual results from each request.
Chaining Requests with switchMap
Another frequent need is to use the result of one request to build the next one. The switchMap operator is perfect for this. Notice the generic parameter passed to get(); it's optional but helps maintain type safety. Without it, the inferred type of the course variable would be Object. By specifying the type, as shown here, the variable is inferred as Course, providing useful auto-completion within the switchMap function.
Let's break down how this chain of HTTP requests executes:
- We start with a source GET request to retrieve a course.
- When this source observable emits, it triggers the mapping function, which creates an inner observable.
- This inner observable is a PUT request that sends the course modifications back to the server.
switchMapreturns a result observable, which we then subscribe to.- The subscription to the result observable is what initiates the subscription to the source GET observable, kicking off the whole process.
- The values emitted by the inner PUT observable are propagated to the result observable.
For a more detailed look at this operator, check out this previous post. switchMap is extremely versatile, as you'll see in the next example.
Getting Results from Two Sequential Requests
In the previous example, the final result observable only contained the response from the second HTTP request, not the first. To get both results, we can use a selector function as a second argument to switchMap. The result observable will then emit an array containing the values from both HTTP calls in the chain.
This selector pattern isn't unique to switchMap; it's a feature available in many other operators. And you're not limited to just two calls—you can keep chaining switchMap and selector functions indefinitely.
Handling HTTP Errors
One of RxJS's core strengths is its robust error handling, which is notoriously tricky in asynchronous programming. While there are many common use cases, a very frequent pattern for HTTP is as follows:
- An HTTP request fails, and its observable emits an error.
- We want to inform the user of this failure.
- We also want to provide a fallback value in the data stream so the UI still has something meaningful to display.
We can implement this with the RxJS catch operator.
First, check the console output for the example:
Error catched
HttpErrorResponse {headers: HttpHeaders, status: 404, statusText: "Not Found", url: "http://localhost:4200/api/simulate-error", ok: false, … }
Value emitted successfully {description: "Error Value Emitted"}
HTTP Observable completed...
Here's what happens step by step:
- The HTTP call fails, and our test server throws an error.
- The
catchoperator catches this exception and calls the error-handling function. - Within this function, we could, for instance, display a user-friendly error message.
- The handler then returns an observable created with the
Observable.of()operator. Observable.of()creates an observable that emits a single value—the supplied argument—and then completes.- The returned observable is subscribed to, and its default value is emitted by the result observable as a fallback.
- Following this, the error observable completes, marking the result observable as complete as well.
Because the catch operator handles the error, the result observable's own error callback would never be invoked.
Using HTTP Interceptors
The new HTTP client introduces interceptors, which allow you to add generic functionality to all your HTTP requests from a single location. This is ideal for cross-cutting concerns, such as adding an authentication token to every request's headers seamlessly.
Let's examine a simple interceptor that adds an auth header:
- It's a standard Angular injectable service, allowing you to inject other services via its constructor.
- Here, we inject a global authentication service that provides the token.
- The
interceptmethod receives two arguments: the current request and anexthandler. - Calling
next.handleis necessary to pass the request down the chain and ultimately make the HTTP call. next.handlereturns an observable, which the interceptor returns as well.- This design mirrors the middleware pattern you might know from libraries like Express.
- Since requests are immutable, you must clone one to add a new header.
- Similarly, the headers object is immutable, so you create a modified copy, for example using
headers.set(). - After these steps, our clone will have a header named
X-CustomAuthHeader. - The modified clone is handed back to the middleware chain, meaning the HTTP call will include the new header.
To activate this interceptor and apply it to all requests, we configure it in our application module by registering it with the HTTP_INTERCEPTORS multi-provider.
Tracking HTTP Progress Events
Another new feature is the ability to listen to request progress events. To receive them, we need to create our HTTP request with observe: 'events' as shown below.
The console output for this type of request would be:
Upload progress event Object {type: 1, loaded: 2, total: 2}
Download progress event Object {type: 3, loaded: 31, total: 31}
Response Received... Object {description: "POST Response"}
This approach yields a comprehensive set of HTTP events, including:
- An initial upload event indicating the request has been fully transmitted.
- A download event signaling that a response from the server is arriving.
- The final response event, which contains the response body.
Summary
The new Angular HTTP Client is a substantial upgrade from its predecessor. It's more intuitive, inherently more type-safe, and introduces valuable new features like interceptors and progress events. Its compatibility alongside the previous module eases the migration process for existing projects.
I hope this guide gives you a strong head start. If you have any questions, please share them in the comments.
For those looking to dive deeper into Angular's core features, the Angular Core Deep Dive course offers a more comprehensive look at the HTTP Client.
If you're new to Angular, the Angular for Beginners Course is a great starting point.
Here are some other popular posts you might find interesting:
- Getting Started With Angular - Development Environment Best Practices With Yarn, the Angular CLI, Setup an IDE
- Why a Single Page Application, What are the Benefits ? What is a SPA ?
- Angular Smart Components vs Presentation Components: What's the Difference, When to Use Each and Why?
- Angular Router - How To Build a Navigation Menu with Bootstrap 4 and Nested Routes
- Angular Router - Extended Guided Tour, Avoid Common Pitfalls
- Angular Components - The Fundamentals
- How to build Angular apps using Observable Data Services - Pitfalls to avoid
- Introduction to Angular Forms - Template Driven vs Model Driven
- Angular ngFor - Learn all Features including trackBy, why is it not only for Arrays ?
- Angular Universal In Practice - How to build SEO Friendly Single Page Apps with Angular
- How does Angular Change Detection Really Work ?
