Current configuration and subscription-driven dispatch
In the consulted Angular 22 documentation, HttpClient is injectable by default from version 21. provideHttpClient configures features including interceptors; it should not be presented as proof that a request occurred. The current baseline uses fetch by default.
Each method such as get returns a cold Observable: creation describes the request and subscription starts it. Two independent subscriptions to the same Observable can produce two requests. If code and template subscribe separately, investigate that duplication before attributing it to a backend bug.
Declared types and accepted data
The generic in http.get<Order> informs TypeScript code of the expected type. It does not verify received JSON has that type, convert numeric strings or confirm authorization. For uncertain data, receive unknown and apply an explicit check before constructing the accepted model.
The example checks a non-null object and a finite numeric total before using toFixed. This check is limited to the field required by the example; it does not certify all possible order attributes or business rules that were never defined.
Latest intent and switchMap
switchMap projects each input value into an inner Observable and replaces the previous subscription with the new one. In the example, successive id choices represent reads and only the current choice’s response matters. Unsubscribing from the previous HTTP request aborts an in-flight client request but does not prove a server undid completed work.
Do not generalize this policy to writes that must complete or be reconciled. First define whether a new event replaces its predecessor, should wait or leaves both results relevant.
Recover a request or end the stream
catchError replaces a stream that ended with an error with the Observable returned by its handler. If placed after switchMap and returning a finite fallback, it can end the external-stream subscription after that fallback. In the example it is inside the request: an error produces failure state for that choice and the subscription to future choices remains.
Placement is a recovery decision, not merely message presentation. A validation failure thrown during map follows the same error path and is not confused with a ready result.
Immutable parameters and interceptors
HttpParams and HttpHeaders use immutable updates: set or append returns a new instance that must be retained or chained. Ignoring the return does not change the original. Functional interceptors configured with withInterceptors follow declared order along the outgoing request; each forwards its intended version through next.
To change HttpRequest headers, use clone with appropriate values. The body has no guarantee against deep mutation. If the same request traverses the chain again, a cumulative body change can be applied again.
Failure, timeout and retry
HttpErrorResponse reports failures through the error channel. Network and timeout errors use status 0; backend error responses carry the received status. The timeout option bounds backend HTTP execution rather than the complete interceptor and processing chain.
Retry resubscribes and can resend the request. For a POST whose response was lost after commit, retrying can duplicate the effect. Policy needs idempotency, result lookup or reconciliation according to the remote contract.
An RxJS operator alone cannot establish exactly one effect in an external system.
View lifetime and acceptance evidence
AsyncPipe supplies a view-managed subscription; the example uses one binding to avoid duplicate presentation-driven requests. Cleaning up the subscription when the view ends avoids callbacks on a destroyed consumer but is not evidence of remote rollback.
To explain behavior, trace choice, subscription, received payload, validation, emitted state and presentation. The example assumes an endpoint and supplies neither backend nor bootstrap. Assessment asks learners to interpret these boundaries and select decisions without installing services or executing excerpts.
// Reading example: assumes a read-only /api/orders/:id endpoint returning {total: number}.
// Angular 22 and RxJS 7-compatible operators; no backend or bootstrap is supplied here.
import {AsyncPipe} from '@angular/common';
import {HttpClient} from '@angular/common/http';
import {Component, inject} from '@angular/core';
import {Subject, catchError, map, of, switchMap} from 'rxjs';
type LookupState = {kind: 'idle' | 'ready' | 'error'; label: string};
function acceptTotal(data: unknown): number {
if (typeof data !== 'object' || data === null) throw new Error('Invalid order');
const total = (data as {total?: unknown}).total;
if (typeof total !== 'number' || !Number.isFinite(total)) throw new Error('Invalid total');
return total;
}
@Component({
selector: 'order-lookup', standalone: true, imports: [AsyncPipe],
template: `<label>Order ID <input #key></label>
<button type="button" (click)="choose(key.value)">Look up</button>
@if (state$ | async; as state) { <p>{{ state.label }}</p> }`,
})
export class OrderLookup {
private readonly http = inject(HttpClient);
private readonly choices = new Subject<string>();
readonly state$ = this.choices.pipe(switchMap(id => {
if (!id) return of<LookupState>({kind: 'idle', label: 'Choose an order'});
return this.http.get<unknown>('/api/orders/' + encodeURIComponent(id)).pipe(
map((data): LookupState => ({kind: 'ready', label: id + ': ' + acceptTotal(data).toFixed(2)})),
catchError(() => of<LookupState>({kind: 'error', label: 'Order could not be accepted'})),
);
}));
choose(id: string) { this.choices.next(id.trim()); }
}One AsyncPipe binding subscribes to state$. Each choice replaces the inner subscription; an empty choice produces idle state. The boundary accepts only a finite numeric total and converts contract or request failures to an explicit message. catchError is inside the request, allowing later choices.
During a new request, the previous message can remain until another state arrives; the example implements no loading indicator.
Common pitfalls
Assuming get sends before subscribe; subscribing twice without recognizing repetition; trusting the generic as a schema; ignoring HttpParams.set’s return; placing catchError outside the stream intended for recovery; treating cancellation as rollback evidence.
Related topics: Forms, validation and user intent · Lifecycle, effects and reactive cleanup · Security boundaries and trusted content
A request has an identity, subscription, data contract and acceptance decision. Keep those responsibilities visible. Cancellation bounds in-flight local work; retrying can create another request; accepting a response requires more than a declared type.
Reference: Current HTTP configuration · Angular 22; standalone components