HTTP communication is essential for modern web applications. This guide covers everything you need to know about handling HTTP requests in Angular, from callbacks and promises to observables and async/await patterns.
Table of Contents
What is HTTPClientModule?
HTTPClientModule provides several powerful features for handling HTTP requests in Angular:
| Feature | Description |
|---|---|
| Typed Responses | Request and receive typed response objects |
| Error Handling | Streamlined error handling and recovery |
| Testability | Built-in features for testing HTTP requests |
| Interceptors | Request and response interception and modification |
| Built-in Cache | Intelligent caching of HTTP responses |
| Timeout Control | Set timeouts for long-running requests |
Synchronous vs Asynchronous Requests
Synchronous HTTP Requests
One task must be executed in a way that is dependent on another task - you must wait for the first task to complete before starting the next one. This creates a blocking pattern where operations are connected or dependent.
Characteristics:
- Blocking: Code waits for execution to complete
- Sequential: Operations happen one after another
- Simpler logic but can freeze the UI
Asynchronous HTTP Requests
Tasks are totally independent and neither one considers the other in any way. Multiple operations can happen concurrently without waiting for each other.
Characteristics:
- Non-blocking: Code continues without waiting
- Concurrent: Multiple operations can run simultaneously
- Better performance and improved UX
- More complex to manage
Callbacks
A callback is a function that is passed into another function as an argument, which is then invoked inside the outer function to complete some kind of action or respond to an event.
Example:
function greeting(name) {
alert("Hello " + name);
}
function processInput(callback) {
var name = prompt("Please enter name", "Harry Potter");
callback(name);
}
processInput(greeting);Use Cases:
- Event handlers
- Asynchronous operations
- Array methods (map, filter, forEach)
Drawback: Callback hell - deeply nested callbacks become difficult to read and maintain
Promises
A promise represents the eventual completion (or failure) of an asynchronous operation and its resulting value. Promises allow you to attach success and failure handlers to async operations.
States:
| State | Description |
|---|---|
| Pending | Initial state, operation hasn’t completed yet |
| Fulfilled | Operation completed successfully |
| Rejected | Operation failed |
Once a promise is fulfilled with a value or rejected with a reason, the associated handlers attached via the then() method are invoked.
Example:
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Success!");
}, 1000);
});
promise
.then((result) => console.log(result))
.catch((error) => console.error(error));Async/Await
Async Functions
The async keyword placed before a function declaration makes that function an async function. It returns a promise automatically.
Example:
async function foo() {
return "Hello World";
}
foo().then(console.log); // Output: Hello WorldAwait
The await keyword pauses execution until a promise settles and returns its result. It can only be used inside async functions.
Syntax:
let value = await promise;Example:
async function foo() {
let promise = new Promise((resolve, reject) => {
setTimeout(() => resolve("Hello World"), 1000);
});
let result = await promise;
console.log(result); // Output: Hello World
}
foo();Benefits:
- Cleaner syntax than promise chains
- Easier error handling with try/catch
- More readable code flow
Key Note:
- Promises use
.then()method for handling results - Observables use
.subscribe()method for handling values
Observables
Observables are a core feature of RxJS that allow you to work with streams of data. They’re particularly powerful in Angular for handling HTTP requests.
Ways to Subscribe to an Observable
1. Observer Object Pattern:
observable.subscribe({
next: (value) => console.log(value),
error: (error) => console.error(error),
complete: () => console.log("Done"),
});2. Separate Function Arguments:
observable.subscribe(
(value) => console.log(value), // next
(error) => console.error(error), // error
() => console.log("Done") // complete
);3. Arrow Functions:
observable.subscribe(
(value) => console.log(value),
(error) => console.error(error)
);Key Observable Methods
// Subscribe with next handler only
observable.subscribe(nextFunc);
// Subscribe with next and error handlers
observable.subscribe(nextFunc, errorFunc);
// Subscribe with all handlers
observable.subscribe(nextFunc, errorFunc, completeFunc);
// Unsubscribe from observable
const subscription = observable.subscribe(nextFunc);
subscription.unsubscribe();Error Handling
Promise Error Handling
1. Using .catch() method:
promise
.then((data) => console.log(data))
.catch((error) => console.error("Error:", error));2. Using try/catch with async/await:
async function fetchData() {
try {
const response = await fetch("/api/data");
const data = await response.json();
console.log(data);
} catch (error) {
console.error("Error:", error);
}
}Observable Error Handling
1. Using error callback:
observable.subscribe(
(data) => console.log(data),
(error) => console.error("Error:", error)
);2. Using RxJS error operators:
import { catchError } from "rxjs/operators";
observable
.pipe(
catchError((error) => {
console.error("Error:", error);
return throwError("Error handled");
})
)
.subscribe((data) => console.log(data));Promises vs Observables
Both promises and observables handle asynchronous operations, but they have key differences:
| Feature | Promise | Observable |
|---|---|---|
| Values Emitted | Single value | Multiple values |
| Execution Time | Eager (immediate) | Lazy (on subscribe) |
| Cancellable | No | Yes (unsubscribe) |
| Sharing | Multicast | Unicast |
| Handler Execution | Asynchronous | Synchronous |
| Subscription | .then() | .subscribe() |
| Best For | Single async result | Data streams |
Single vs Multiple Values
- Promise: Emits a single value
- Observable: Can emit multiple values over time
// Promise - single value
const promise = Promise.resolve(42);
promise.then((value) => console.log(value)); // 42
// Observable - multiple values
const observable = interval(1000).pipe(take(3));
observable.subscribe((value) => console.log(value)); // 0, 1, 2Eager vs Lazy
- Promise: Eager - executor function runs immediately when promise is created
- Observable: Lazy - executor function runs only when subscriber subscribes
Non-cancelable vs Cancelable
- Promise: Cannot be cancelled once created
- Observable: Can be cancelled via
unsubscribe()
const subscription = observable.subscribe(nextFunc);
subscription.unsubscribe(); // Cancel the observableMulticast vs Unicast
- Promise: All
.then()calls share the same execution - Observable: Each
.subscribe()call gets its own execution
Async Handlers vs Sync Handlers
- Promise: Handlers execute asynchronously (after all synchronous code)
- Observable: Handlers execute synchronously (within the current execution flow)
Best Practices
Promise Best Practices
-
Always handle rejections:
typescriptpromise.catch((error) => console.error(error)); -
Use async/await for cleaner code:
typescriptasync function getData() { try { const data = await fetchData(); return data; } catch (error) { console.error(error); } }
Observable Best Practices
-
Always unsubscribe to prevent memory leaks:
typescriptconst subscription = observable.subscribe(nextFunc); ngOnDestroy() { subscription.unsubscribe(); } -
Use async pipe in templates:
<div>{{ observable$ | async }}</div> -
Use RxJS operators for transformations:
typescriptobservable .pipe( map((value) => value * 2), filter((value) => value > 10) ) .subscribe(console.log);
HTTP Requests
For HTTP requests in Angular, prefer observables with proper error handling:
this.http
.get("/api/data")
.pipe(
map((response) => response.data),
catchError((error) => {
console.error("HTTP Error:", error);
return throwError("Failed to fetch data");
})
)
.subscribe(
(data) => console.log(data),
(error) => console.error(error)
);