-
Notifications
You must be signed in to change notification settings - Fork 26.6k
Description
Which @angular/* package(s) are relevant/related to the feature request?
No response
Description
Angular's current HTTP client requires developers to use RxJS Observables for all HTTP operations, even simple request-response patterns. This creates unnecessary complexity for common API calls:
Current approach:
typescript// Requires RxJS knowledge and imports
import { firstValueFrom } from 'rxjs';
const result = await firstValueFrom(this.http.get<User[]>('/api/users'));
Impact on developer experience:
Steep learning curve for developers from other frameworks
Verbose code for simple HTTP operations
Cognitive overhead of understanding Observables for basic API calls
Inconsistent patterns across teams (some use .subscribe(), others firstValueFrom)
Proposed Solution
Add simple Promise-based helper functions that wrap the existing HttpClient:
typescript// Proposed simple API
const users = await callApiGet<User[]>('/api/users');
const newUser = await callApiPost('/api/users', { name: 'John' });
Benefits
Lower barrier to entry - Developers familiar with fetch() or other HTTP libraries can be immediately productive
Reduced boilerplate - Eliminates need for RxJS imports and conversions in 80% of HTTP use cases
Consistent patterns - One clear way to make simple API calls
Backward compatibility - Existing HttpClient and Observable patterns remain unchanged
Type safety - Maintains Angular's TypeScript benefits with generic type parameters
Implementation Details
Functions would be thin wrappers around existing HttpClient
No breaking changes to current APIs
Optional adoption - teams can choose when to use them
Maintains all existing HttpClient features (interceptors, error handling, etc.)
Target Audience
New Angular developers coming from other frameworks
Teams building standard CRUD applications
Developers who prefer imperative over reactive programming patterns
Projects where Observable complexity isn't justified
Precedent
Other major frameworks provide simple HTTP APIs by default:
React: fetch() or axios with async/await
Vue: axios or fetch() with Promises
Svelte: fetch() with async/await
This proposal brings Angular's developer experience closer to industry standards while preserving its powerful Observable capabilities for complex scenarios.
Proposed solution
import { inject } from '@angular/core';
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
import { firstValueFrom } from 'rxjs';
// Simple options interface
export interface SimpleHttpOptions {
headers?: Record<string, string>;
params?: Record<string, string | number | boolean>;
responseType?: 'json' | 'text' | 'blob';
}
// GET helper function
export const callApiGet = <T = any>(url: string, options?: SimpleHttpOptions): Promise => {
const http = inject(HttpClient);
// Build HttpClient options
const httpOptions: any = {};
if (options?.headers) {
httpOptions.headers = new HttpHeaders(options.headers);
}
if (options?.params) {
httpOptions.params = new HttpParams();
Object.keys(options.params).forEach(key => {
httpOptions.params = httpOptions.params.set(key, options.params![key].toString());
});
}
if (options?.responseType) {
httpOptions.responseType = options.responseType;
}
return firstValueFrom(http.get(url, httpOptions));
};
// POST helper function
export const callApiPost = <T = any>(url: string, body: any = null, options?: SimpleHttpOptions): Promise => {
const http = inject(HttpClient);
// Build HttpClient options
const httpOptions: any = {};
if (options?.headers) {
httpOptions.headers = new HttpHeaders(options.headers);
}
if (options?.params) {
httpOptions.params = new HttpParams();
Object.keys(options.params).forEach(key => {
httpOptions.params = httpOptions.params.set(key, options.params![key].toString());
});
}
if (options?.responseType) {
httpOptions.responseType = options.responseType;
}
return firstValueFrom(http.post(url, body, httpOptions));
};
// PUT helper function (bonus)
export const callApiPut = <T = any>(url: string, body: any = null, options?: SimpleHttpOptions): Promise => {
const http = inject(HttpClient);
const httpOptions: any = {};
if (options?.headers) {
httpOptions.headers = new HttpHeaders(options.headers);
}
if (options?.params) {
httpOptions.params = new HttpParams();
Object.keys(options.params).forEach(key => {
httpOptions.params = httpOptions.params.set(key, options.params![key].toString());
});
}
if (options?.responseType) {
httpOptions.responseType = options.responseType;
}
return firstValueFrom(http.put(url, body, httpOptions));
};
// DELETE helper function (bonus)
export const callApiDelete = <T = any>(url: string, options?: SimpleHttpOptions): Promise => {
const http = inject(HttpClient);
const httpOptions: any = {};
if (options?.headers) {
httpOptions.headers = new HttpHeaders(options.headers);
}
if (options?.params) {
httpOptions.params = new HttpParams();
Object.keys(options.params).forEach(key => {
httpOptions.params = httpOptions.params.set(key, options.params![key].toString());
});
}
if (options?.responseType) {
httpOptions.responseType = options.responseType;
}
return firstValueFrom(http.delete(url, httpOptions));
};
Alternatives considered
Alternatives Considered
- Do Nothing (Status Quo)
Description: Keep current Observable-only HTTP approach
Pros: No additional maintenance burden, maintains consistency
Cons: Continues developer experience problems, high learning curve for newcomers
Rejected because: Ignores legitimate usability concerns affecting developer productivity - Replace HttpClient Entirely with Promise-Based API
Description: Deprecate Observable HttpClient and create new Promise-based client
Pros: Clean, simple API without Observable complexity
Cons: Massive breaking change, loses Observable benefits for complex scenarios, huge migration effort
Rejected because: Would break existing applications and eliminate legitimate use cases for reactive programming - Add Promise Methods Directly to HttpClient
Description: Extend HttpClient with .getPromise(), .postPromise() methods
Pros: Integrated into existing service, familiar injection pattern
Cons: Bloats HttpClient interface, mixing paradigms in same class creates confusion
Rejected because: Violates single responsibility principle and creates API inconsistency - Third-Party Library Solution
Description: Let community create separate HTTP libraries (like axios for Angular)
Pros: No framework maintenance burden, market-driven solutions
Cons: Fragments ecosystem, bypasses Angular's interceptor system, inconsistent patterns across projects
Rejected because: Loses integration benefits and creates ecosystem fragmentation - HTTP Utilities Package (Chosen Approach)
Description: Separate utility functions that wrap existing HttpClient
Pros:
Optional adoption
No breaking changes
Preserves Observable capabilities
Simple to implement and maintain
Can coexist with existing patterns
Selected because: Provides developer experience improvements without architectural disruption or breaking changes.
This keeps the focus on technical alternatives rather than suggesting the problem is just a matter of education or training.