Enhanced HTTP client with loading state management, request utilities, decorator-based API support, and comprehensive request/response handling for Angular applications.
Enhanced HTTP client service with loading state tracking and utility methods.
/**
* Enhanced HTTP client with loading state and utilities
* Extends Angular HttpClient with additional features
*/
class _HttpClient {
/** Whether any HTTP request is currently loading */
readonly loading: boolean;
/** Current number of active HTTP requests */
readonly loadingCount: number;
/** Perform GET request */
get<T>(url: string, params?: any, options?: any): Observable<T>;
/** Perform POST request */
post<T>(url: string, body?: any, params?: any, options?: any): Observable<T>;
/** Perform DELETE request */
delete<T>(url: string, params?: any, options?: any): Observable<T>;
/** Perform PATCH request */
patch<T>(url: string, body?: any, params?: any, options?: any): Observable<T>;
/** Perform PUT request */
put<T>(url: string, body?: any, params?: any, options?: any): Observable<T>;
/** Perform form-encoded request */
form<T>(url: string, body?: any, params?: any, options?: any): Observable<T>;
/** Perform JSONP request */
jsonp(url: string, params?: any, callbackParam?: string): Observable<any>;
/** Generic request method */
request<T>(method: string, url: string, options?: any): Observable<T>;
/** Parse parameters into HttpParams */
parseParams(params: any): HttpParams;
/** Apply parameters to URL */
appliedUrl(url: string, params?: any): string;
/** Clear loading count manually */
cleanLoading(): void;
}Usage Examples:
import { Component, inject } from "@angular/core";
import { _HttpClient } from "@delon/theme";
interface User {
id: number;
name: string;
email: string;
}
interface CreateUserRequest {
name: string;
email: string;
}
@Component({
selector: "user-service",
template: `
<div class="loading-indicator" *ngIf="http.loading">
Loading... ({{ http.loadingCount }} requests)
</div>
<button (click)="loadUsers()">Load Users</button>
<button (click)="createUser()">Create User</button>
`
})
export class UserServiceComponent {
http = inject(_HttpClient);
users: User[] = [];
loadUsers() {
// GET request with query parameters
this.http.get<User[]>('/api/users', {
page: 1,
limit: 10,
search: 'john'
}).subscribe(users => {
this.users = users;
console.log('Loaded users:', users);
});
}
createUser() {
const userData: CreateUserRequest = {
name: 'John Doe',
email: 'john@example.com'
};
// POST request with body
this.http.post<User>('/api/users', userData).subscribe(newUser => {
this.users.push(newUser);
console.log('Created user:', newUser);
});
}
updateUser(id: number, updates: Partial<User>) {
// PATCH request for partial updates
this.http.patch<User>(`/api/users/${id}`, updates).subscribe(updatedUser => {
const index = this.users.findIndex(u => u.id === id);
if (index >= 0) {
this.users[index] = updatedUser;
}
});
}
deleteUser(id: number) {
// DELETE request
this.http.delete(`/api/users/${id}`).subscribe(() => {
this.users = this.users.filter(u => u.id !== id);
console.log('Deleted user:', id);
});
}
uploadFile(file: File) {
const formData = new FormData();
formData.append('file', file);
formData.append('description', 'User avatar');
// Form request for file upload
this.http.form<{ url: string }>('/api/upload', formData).subscribe(result => {
console.log('File uploaded:', result.url);
});
}
searchWithJsonp(query: string) {
// JSONP request for cross-origin requests
this.http.jsonp('/api/search', { q: query }, 'callback').subscribe(results => {
console.log('Search results:', results);
});
}
}Decorator-based API for creating type-safe HTTP services with automatic parameter binding.
/**
* Base class for decorator-based HTTP APIs
* Extend this class to create declarative HTTP services
*/
class BaseApi {
constructor(protected http: _HttpClient) {}
}
/** Set base URL for all requests in the class */
function BaseUrl(url: string): ClassDecorator;
/** Set default headers for all requests in the class */
function BaseHeaders(headers: any): ClassDecorator;
/** Mark parameter as URL path parameter */
function Path(key?: string): ParameterDecorator;
/** Mark parameter as query string parameter */
function Query(key?: string): ParameterDecorator;
/** Mark parameter as request body */
function Body(): ParameterDecorator;
/** Mark parameter as headers */
function Headers(key?: string): ParameterDecorator;
/** Mark parameter as payload (auto-detects body vs query) */
function Payload(): ParameterDecorator;
/** HTTP method decorators */
function GET(url?: string, options?: any): MethodDecorator;
function POST(url?: string, options?: any): MethodDecorator;
function PUT(url?: string, options?: any): MethodDecorator;
function DELETE(url?: string, options?: any): MethodDecorator;
function PATCH(url?: string, options?: any): MethodDecorator;
function HEAD(url?: string, options?: any): MethodDecorator;
function OPTIONS(url?: string, options?: any): MethodDecorator;
function JSONP(url?: string, options?: any): MethodDecorator;
function FORM(url?: string, options?: any): MethodDecorator;Usage Examples:
import { Injectable } from "@angular/core";
import { Observable } from "rxjs";
import {
BaseApi,
BaseUrl,
BaseHeaders,
GET,
POST,
PUT,
DELETE,
Path,
Query,
Body,
Headers
} from "@delon/theme";
// Define API service using decorators
@Injectable()
@BaseUrl('/api/v1')
@BaseHeaders({
'Content-Type': 'application/json',
'Accept': 'application/json'
})
export class UserApiService extends BaseApi {
@GET('/users')
getUsers(
@Query('page') page: number,
@Query('limit') limit: number,
@Query('search') search?: string
): Observable<User[]> {
return null as any; // Implementation handled by decorator
}
@GET('/users/{id}')
getUserById(@Path('id') id: number): Observable<User> {
return null as any;
}
@POST('/users')
createUser(@Body() user: CreateUserRequest): Observable<User> {
return null as any;
}
@PUT('/users/{id}')
updateUser(
@Path('id') id: number,
@Body() user: Partial<User>
): Observable<User> {
return null as any;
}
@DELETE('/users/{id}')
deleteUser(@Path('id') id: number): Observable<void> {
return null as any;
}
@GET('/users/{id}/posts')
getUserPosts(
@Path('id') userId: number,
@Query('published') publishedOnly?: boolean,
@Headers('Authorization') authToken?: string
): Observable<Post[]> {
return null as any;
}
@FORM('/users/{id}/avatar')
uploadAvatar(
@Path('id') id: number,
@Body() formData: FormData
): Observable<{ url: string }> {
return null as any;
}
}
// Use the decorated service
@Component({
selector: "user-manager",
providers: [UserApiService]
})
export class UserManagerComponent {
constructor(private userApi: UserApiService) {}
ngOnInit() {
// Decorated methods work like normal service methods
this.userApi.getUsers(1, 10, 'john').subscribe(users => {
console.log('Users from decorated API:', users);
});
this.userApi.getUserById(1).subscribe(user => {
console.log('User details:', user);
});
}
createNewUser() {
const newUser: CreateUserRequest = {
name: 'Jane Smith',
email: 'jane@example.com'
};
this.userApi.createUser(newUser).subscribe(created => {
console.log('Created user via decorator:', created);
});
}
}Special context tokens for customizing HTTP request behavior.
/**
* HTTP context tokens for request customization
* Use with HttpContext to modify request behavior
*/
/** Skip custom error handling for specific requests */
const CUSTOM_ERROR: HttpContextToken<boolean>;
/** Ignore API base URL prefix for specific requests */
const IGNORE_BASE_URL: HttpContextToken<boolean>;
/** Return raw response body without parsing */
const RAW_BODY: HttpContextToken<boolean>;Usage Examples:
import { HttpContext } from "@angular/common/http";
import { _HttpClient, CUSTOM_ERROR, IGNORE_BASE_URL, RAW_BODY } from "@delon/theme";
@Component({})
export class HttpContextExample {
constructor(private http: _HttpClient) {}
skipErrorHandling() {
// Skip custom error interceptor for this request
const context = new HttpContext().set(CUSTOM_ERROR, true);
this.http.get('/api/risky-endpoint', {}, { context }).subscribe({
next: data => console.log('Success:', data),
error: err => {
// Handle error manually since custom handler was skipped
console.error('Manual error handling:', err);
}
});
}
useExternalApi() {
// Ignore base URL for external API calls
const context = new HttpContext().set(IGNORE_BASE_URL, true);
this.http.get('https://external-api.com/data', {}, { context })
.subscribe(data => {
console.log('External API data:', data);
});
}
getRawResponse() {
// Get raw response without JSON parsing
const context = new HttpContext().set(RAW_BODY, true);
this.http.get('/api/raw-data', {}, { context }).subscribe(raw => {
console.log('Raw response:', raw);
// Process raw response manually
});
}
combinedContext() {
// Use multiple context tokens
const context = new HttpContext()
.set(CUSTOM_ERROR, true)
.set(IGNORE_BASE_URL, true)
.set(RAW_BODY, true);
this.http.get('https://external-api.com/raw', {}, { context })
.subscribe(response => {
console.log('Combined context response:', response);
});
}
}Utility methods for URL manipulation and parameter handling.
/**
* Parse parameters into HttpParams object
* Handles nested objects and arrays
*/
parseParams(params: any): HttpParams;
/**
* Apply parameters to URL
* Appends query string parameters to URL
*/
appliedUrl(url: string, params?: any): string;
/**
* Clear loading count manually
* Use when loading state gets out of sync
*/
cleanLoading(): void;Usage Examples:
import { _HttpClient } from "@delon/theme";
@Component({})
export class HttpUtilitiesExample {
constructor(private http: _HttpClient) {}
demonstrateParseParams() {
// Complex parameters
const complexParams = {
page: 1,
filters: {
category: 'books',
price: { min: 10, max: 50 }
},
tags: ['fiction', 'mystery'],
published: true
};
const httpParams = this.http.parseParams(complexParams);
console.log('Parsed params:', httpParams.toString());
// Output: page=1&filters.category=books&filters.price.min=10&filters.price.max=50&tags=fiction&tags=mystery&published=true
}
demonstrateAppliedUrl() {
const baseUrl = '/api/search';
const params = {
q: 'angular',
type: 'tutorial',
limit: 10
};
const fullUrl = this.http.appliedUrl(baseUrl, params);
console.log('Full URL:', fullUrl);
// Output: /api/search?q=angular&type=tutorial&limit=10
}
handleLoadingState() {
console.log('Current loading state:', this.http.loading);
console.log('Active requests:', this.http.loadingCount);
// If loading state gets stuck, manually clear it
if (this.http.loadingCount > 0 && !this.http.loading) {
this.http.cleanLoading();
console.log('Loading state cleared');
}
}
buildDynamicRequest() {
const endpoint = '/api/products';
const searchParams = {
category: 'electronics',
price_range: '100-500',
in_stock: true
};
// Build URL with parameters
const requestUrl = this.http.appliedUrl(endpoint, searchParams);
// Make request
this.http.get(requestUrl).subscribe(products => {
console.log('Products:', products);
});
}
}