Headers, Params & Options
Every call you make with Angular’s HttpClient accepts an optional options object that lets you shape the request and the response. Through it you attach authentication and content headers, append query parameters in a type-safe way, choose whether you want the parsed body or the full HTTP response, and opt into sending cookies for cross-origin calls. Understanding these options is what turns a naive get() into a production-ready API integration.
Setting headers with HttpHeaders
HttpHeaders is an immutable class. Each mutation method (set, append, delete) returns a brand-new instance rather than changing the original, so you must chain calls or capture the return value. This immutability makes headers safe to share across requests without accidental leakage.
import { inject } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
export class ArticleApi {
private http = inject(HttpClient);
create(article: Article) {
const headers = new HttpHeaders()
.set('Content-Type', 'application/json')
.set('Authorization', `Bearer ${this.token}`)
.append('X-Trace-Id', crypto.randomUUID());
return this.http.post<Article>('/api/articles', article, { headers });
}
}
You can also build headers from a plain object literal, which Angular converts internally. This is the most concise form when you do not need conditional logic:
return this.http.get<Article[]>('/api/articles', {
headers: { 'Authorization': `Bearer ${this.token}`, 'Accept': 'application/json' },
});
Cross-cutting headers like
Authorizationbelong in a functional interceptor, not scattered across services. Set per-request headers here only when they are genuinely specific to that one call.
Adding query parameters with HttpParams
HttpParams mirrors HttpHeaders: it is immutable and URL-encodes values for you, so you never hand-concatenate query strings. Use set to replace a key and append to add repeated keys (handy for array-style filters).
import { HttpParams } from '@angular/common/http';
search(term: string, page: number, tags: string[]) {
let params = new HttpParams()
.set('q', term)
.set('page', page) // numbers and booleans are accepted
.set('size', 20);
for (const tag of tags) {
params = params.append('tag', tag);
}
return this.http.get<Article[]>('/api/articles', { params });
}
A request for search('rxjs', 2, ['ng', 'http']) produces:
Output:
GET /api/articles?q=rxjs&page=2&size=20&tag=ng&tag=http
The object-literal shorthand works for params too. Use it for flat, unconditional queries:
this.http.get<Article[]>('/api/articles', {
params: { q: term, page, size: 20 },
});
HttpParamsis immutable —params.set(...)returns a new instance. Forgetting to reassign (params = params.set(...)) is the single most common params bug.
Response type and the observe option
By default HttpClient parses the response body as JSON and emits just that body. Two options change this behaviour.
The responseType option controls how the body is interpreted. The observe option controls how much of the HTTP exchange you receive.
| Option | Values | Effect |
|---|---|---|
responseType | 'json' (default), 'text', 'blob', 'arraybuffer' | How the body is deserialized |
observe | 'body' (default), 'response', 'events' | Emit the body, the full HttpResponse, or a stream of HttpEvents |
Reading a non-JSON payload such as a CSV export requires responseType: 'text':
downloadCsv() {
return this.http.get('/api/articles/export', { responseType: 'text' });
// Observable<string>
}
When you need status codes or response headers, set observe: 'response' to receive the full HttpResponse<T>:
load(id: string) {
return this.http.get<Article>(`/api/articles/${id}`, { observe: 'response' }).pipe(
tap((res) => console.log('ETag:', res.headers.get('ETag'))),
map((res) => res.body!),
);
}
Output:
ETag: "a1b2c3d4"
Setting observe: 'events' emits every HttpEvent in order — useful for tracking upload/download progress via reportProgress: true.
Sending credentials and other options
For cross-origin requests where the browser must include cookies (and honour Set-Cookie responses), set withCredentials: true. Your server must answer with Access-Control-Allow-Credentials: true and an explicit origin for this to work.
this.http.get<Session>('/api/session', { withCredentials: true });
You can combine any of these options in a single call. Angular’s overloaded typings infer the correct return type from the options you pass:
this.http.post<Article>('/api/articles', body, {
headers: { 'X-Idempotency-Key': key },
params: { draft: true },
observe: 'response',
withCredentials: true,
});
// Observable<HttpResponse<Article>>
Best practices
- Treat
HttpHeadersandHttpParamsas immutable — always reassign or chain the returned instance. - Push authentication and tracing headers into a functional interceptor; keep per-request options minimal.
- Prefer
HttpParams(or the object shorthand) over manual string concatenation so values are encoded correctly. - Reach for
observe: 'response'only when you genuinely need status codes or response headers; otherwise let Angular emit the body. - Match
responseTypeto the payload — never request'json'for text, blob, or binary endpoints. - Enable
withCredentialsdeliberately, and confirm the matching CORS headers exist on the server. - Let Angular infer return types from your options rather than casting; the overloads encode the correct shape.