@blockquote-web-components/ajax-provider
v1.6.0
Published
A modern architecture for building composable and reusable Web Components
Maintainers
Readme
AJAX Provider Documentation
A robust, event-driven HTTP request service that bridges the XMLHttpRequest Web API with RxJS 9 cold observables and native Promises.
Table of Contents
- Overview
- Architecture: Why XMLHttpRequest + RxJS 9?
- API Selection: Promises vs Observables
- Installation
- Usage Examples
- Lifecycle Events
- API Reference
Overview
AjaxProvider extends EventTarget (via AjaxProviderMixin) to deliver an event-based, observable HTTP client for modern web components and applications. It allows developers to choose between an async/await Promise interface or a fully reactive RxJS 9 Observable stream.
Architecture: Why XMLHttpRequest + RxJS 9?
Modern web development frequently defaults to fetch(), but XMLHttpRequest (XHR) paired with RxJS 9 provides unique capabilities essential for robust enterprise web applications:
- Upload Progress Tracking: Unlike standard
fetch(),XMLHttpRequestexposesxhr.upload.onprogress. This allowsAjaxProviderto report real-time percentage updates when uploading large files,FormData, orArrayBufferpayloads. - Immediate Socket Teardown: Calling
xhr.abort()instantly terminates the underlying TCP/HTTP connection, guaranteeing zero resource waste upon cancellation. - Cold Observable Execution: Requests created via
request$()are lazy. The network request is deferred until a subscriber calls.subscribe(). Re-subscribing re-executes the request cleanly. - Automatic Teardown: Unsubscribing from a
request$()observable automatically triggersxhr.abort(), preventing memory leaks and race conditions.
API Selection: Promises vs Observables
AjaxProvider provides two primary execution methods depending on your application needs:
| Feature | generateRequest() (Promise) | request$() (Observable) |
| :--- | :--- | :--- |
| Return Type | Promise<AjaxResponse> | Observable<AjaxResponse> |
| Primary Use Case | Standard single-response async/await flows | Complex reactive flows, streaming & cancellation |
| RxJS Operators | N/A | Full support (retry, switchMap, debounceTime) |
| Cancellation | Not directly cancelable | Cancelable via unsubscribe() or AbortSignal |
| Progress Events | Dispatches ajaxprogress events | Dispatches ajaxprogress & emits progress items |
| Internal Engine | Wraps request$() via lastValueFrom() | Native RxJS fromXMLHttpRequest stream |
Note:
generateRequest()internally callslastValueFrom(this.request$()). Both APIs share the exact same underlying RxJS pipeline.
Installation
npm install \@blockquote-web-components/ajax-providerUsage Examples
1. Promise-based Request (generateRequest)
Ideal for straightforward fetch operations using async/await:
import { AjaxProvider } from '\@blockquote-web-components/ajax-provider';
const provider = new AjaxProvider({
url: 'https://httpbingo.org',
path: 'get',
queryParams: { search: 'lit-element' },
});
try {
const response = await provider.generateRequest();
console.log('Response status:', response.status);
console.log('Response body:', response.response);
} catch (error) {
console.error('Request failed:', error.message);
}2. Reactive Request with Retry (request$)
Leverage RxJS operators to handle automatic retries on server failures (e.g., HTTP 500):
import { pipe } from 'rxjs/pipe';
import { retry } from 'rxjs/retry';
import { AjaxProvider } from '\@blockquote-web-components/ajax-provider';
const provider = new AjaxProvider({
url: 'https://httpbingo.org',
path: 'status/500',
});
// Automatically retry up to 2 additional times before erroring out
provider
.request$()
[pipe]((values) => values[retry]({ count: 2 }))
.subscribe({
next: (res) => console.log('Success:', res),
error: (err) => console.error('Failed after 3 attempts:', err),
});3. Progress Monitoring (Upload & Download)
Track progress percentages during file uploads or large payload downloads:
const formData = new FormData();
formData.append('file', fileInput.files[0]);
const provider = new AjaxProvider({
url: 'https://httpbingo.org',
path: 'post',
method: 'POST',
body: formData,
includeUploadProgress: true,
includeDownloadProgress: true,
});
provider.addEventListener('ajaxprogress', ({ detail }) => {
const { type, loaded, total } = detail;
const percent = total ? Math.round((loaded / total) * 100) : 0;
console.log(`[${type}] ${percent}% (${loaded}/${total} bytes)`);
});
await provider.generateRequest();4. Request Cancellation
Cancel an in-flight HTTP request cleanly using AbortController:
const controller = new AbortController();
const provider = new AjaxProvider({
url: 'https://httpbingo.org',
path: 'drip',
queryParams: 'duration=5&delay=0&numbytes=1024',
});
provider.request$().subscribe({
next: (res) => console.log('Data:', res),
error: (err) => console.log('Aborted or failed:', err),
}, { signal: controller.signal });
// Cancel the request after 1 second
setTimeout(() => controller.abort(), 1000);5. Event-Driven Request Chaining
Chain dependent requests using EventTarget.when() and RxJS switchMap:
import { pipe } from 'rxjs/pipe';
import { switchMap } from 'rxjs/switch-map';
const requestAuth = new AjaxProvider({ url: 'https://httpbingo.org', path: 'uuid' });
const requestData = new AjaxProvider({ url: 'https://httpbingo.org' });
requestAuth
.when('ajaxresponse')
[pipe]((values) =>
values[switchMap](({ detail }) => {
requestData.path = `anything/${detail.response.uuid}`;
return requestData.request$();
})
)
.subscribe({
next: (result) => console.log('Chained response:', result),
});
requestAuth.generateRequest();Lifecycle Events
AjaxProvider dispatches standard custom events on its EventTarget context during execution (identically for both generateRequest() and request$()):
ajaxpresend: Fired right before the request is opened.ajaxprogress: Fired when upload or download progress events occur.ajaxresponse: Fired when a successful response (status < 400) is received.ajaxresponseend: Fired after a successful response completes.ajaxerror: Fired when a request error or HTTP status>= 400occurs.ajaxerrorend: Fired after error handling finishes.
API Reference
Request Configuration Options (AjaxProviderConfig)
url: The base URL for the AJAX request. (string)path: Path to append to the base URL (e.g.'users'). (string)method: The HTTP request method (e.g.,'GET','POST','PUT','DELETE','PATCH'). Default:'GET'. (string)headers: Custom headers for the request. Default headers:Accept:'application/json, text/plain, *\/*; q=0.01'Content-Type:'application/json'
body: Request payload (JSON object,FormData, string,Blob,ArrayBuffer, etc.).queryParams: Query parameters (object or string) appended to the request URL. (Object|string|undefined)async: Whether to send the request asynchronously. Default:true. (boolean)timeout: Request timeout in milliseconds. Default:0(no timeout). (number)user: Username for HTTP basic authentication. (string)password: Password for HTTP basic authentication. (string)withCredentials: Indicates whether to include cross-site credentials with the request. Default:false. (boolean)xsrfCookieName: The name of the XSRF cookie. (string)xsrfHeaderName: The name of the XSRF header. (string)responseType: The response type ('json','text','blob','arraybuffer','document'). Default:'json'. (string)includeDownloadProgress: Indicates whether to include download progress events (ajaxprogress). Default:false. (boolean)includeUploadProgress: Indicates whether to include upload progress events (ajaxprogress). Default:false. (boolean)avoidBoundary: Set totrueto stop delegating boundary management for multipart requests to the browser. Default:false. (boolean)dispatchEventContext: The context for dispatching custom events. Default:this.customEventPrefix: Custom event prefix for lifecycle events. Default:'ajax'. (string)
Instance Properties
lastResponse: Stores the lastAjaxResponseobject received. (AjaxResponse|undefined)lastError: Stores the lastAjaxErrorobject encountered. (AjaxError|undefined)
Methods
generateRequest(): Generates and sends the AJAX request, returning aPromise<AjaxResponse>.request$(): Returns a cold RxJSObservable<AjaxResponse>that executes the AJAX request per subscription.
src/AjaxProvider.ts:
class: AjaxProvider
Mixins
| Name | Module | Package |
| ------------------- | ------------------------- | ------- |
| AjaxProviderMixin | /src/AjaxProviderMixin.js | |
Fields
| Name | Privacy | Type | Default | Description | Inherited From |
| ------------------------- | ------- | -------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- |
| dispatchEventContext | | {dispatchEvent(event: CustomEvent): void} \| undefined | this | The context for dispatching events. | AjaxProviderMixin |
| lastResponse | | AjaxResponse<R> \| undefined | undefined | The last AJAX response object. | AjaxProviderMixin |
| lastError | | AjaxError \| undefined | undefined | The last error object. | AjaxProviderMixin |
| customEventPrefix | | string \| undefined | 'ajax' | A custom event prefix for events related to HTTP requests. | AjaxProviderMixin |
| avoidBoundary | | boolean \| undefined | false | Set to `true` to stop delegating the use of boundaries for multipart requests to the browser.
Only change this to `true` if you know what you are doing. | AjaxProviderMixin |
| url | | string | '' | | AjaxProviderMixin |
| path | | string | '' | | AjaxProviderMixin |
| body | | unknown | undefined | | AjaxProviderMixin |
| async | | boolean | true | | AjaxProviderMixin |
| method | | string | 'GET' | | AjaxProviderMixin |
| _headers | | Record<string, string> | { Accept: 'application/json, text/plain, */*; q=0.01', 'Content-Type': 'application/json', } | | AjaxProviderMixin |
| headers | | Record<string, string> \| undefined | undefined | | AjaxProviderMixin |
| timeout | | number | 0 | | AjaxProviderMixin |
| user | | string | '' | | AjaxProviderMixin |
| password | | string | '' | | AjaxProviderMixin |
| withCredentials | | boolean | false | | AjaxProviderMixin |
| xsrfCookieName | | string | '' | | AjaxProviderMixin |
| xsrfHeaderName | | string | '' | | AjaxProviderMixin |
| responseType | | string | '' | | AjaxProviderMixin |
| queryParams | | unknown \| undefined | undefined | | AjaxProviderMixin |
| includeDownloadProgress | | boolean | false | | AjaxProviderMixin |
| includeUploadProgress | | boolean | false | | AjaxProviderMixin |
Methods
| Name | Privacy | Description | Parameters | Return | Inherited From |
| --------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- | --------------------------------- | ----------------- |
| _assignAjaxProviderConfig | | Assigns configuration options to the AjaxProvider instance. | config: AjaxProviderConfig | void | |
| request$ | | Returns a cold Observable that performs the AJAX request on each subscription.
Progress is reported through the `ajaxprogress` event. Errors are reported
through `ajaxerror`/`ajaxerrorend` events before the observable errors. | | ColdObservable<AjaxResponse<R>> | AjaxProviderMixin |
| generateRequest | | Generates and sends the AJAX request. | | Promise<AjaxResponse<R>> | AjaxProviderMixin |
Methods
| Name | Privacy | Description | Parameters | Return | Inherited From |
| ----------------------- | ------- | --------------------------------------------------------------- | -------------------------- | -------- | ----------------- |
| _assignAjaxRxjsConfig | private | Assigns the configuration settings for the AJAX request. | | Object | AjaxProviderMixin |
| _joinUrlData | private | Joins the base URL and path to create the complete request URL. | | string | AjaxProviderMixin |
| _joinHeaders | private | Joins the default headers with custom headers. | formData: unknown | Object | AjaxProviderMixin |
| _dispatchEvent | private | Dispatches a custom event with the specified type and payload. | type: string, payload: * | void | AjaxProviderMixin |
Exports
| Kind | Name | Declaration | Module | Package |
| ---- | -------------- | ------------ | ------------------- | ------- |
| js | AjaxProvider | AjaxProvider | src/AjaxProvider.ts | |
src/AjaxProviderMixin.ts:
mixin: AjaxProviderMixin
Mixins
| Name | Module | Package |
| ------------- | ------ | --------------------- |
| dedupeMixin | | @open-wc/dedupe-mixin |
Parameters
| Name | Type | Default | Description |
| ------ | ---- | ------- | ----------- |
| Base | T | | |
Fields
| Name | Privacy | Type | Default | Description | Inherited From |
| ------------------------- | ------- | -------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | ----------- | -------------- |
| url | | string | '' | | |
| path | | string | '' | | |
| body | | unknown | undefined | | |
| async | | boolean | true | | |
| method | | string | 'GET' | | |
| _headers | | Record<string, string> | { Accept: 'application/json, text/plain, */*; q=0.01', 'Content-Type': 'application/json', } | | |
| headers | | Record<string, string> \| undefined | undefined | | |
| timeout | | number | 0 | | |
| user | | string | '' | | |
| password | | string | '' | | |
| withCredentials | | boolean | false | | |
| xsrfCookieName | | string | '' | | |
| xsrfHeaderName | | string | '' | | |
| responseType | | string | '' | | |
| queryParams | | unknown \| undefined | undefined | | |
| includeDownloadProgress | | boolean | false | | |
| includeUploadProgress | | boolean | false | | |
| avoidBoundary | | boolean \| undefined | | | |
| dispatchEventContext | | {dispatchEvent(event: CustomEvent): void} \| undefined | | | |
| customEventPrefix | | string \| undefined | | | |
| lastResponse | | AjaxResponse<R> \| undefined | | | |
| lastError | | AjaxError \| undefined | | | |
Methods
| Name | Privacy | Description | Parameters | Return | Inherited From |
| ----------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | --------------------------------- | -------------- |
| request$ | | Returns a cold Observable that performs the AJAX request on each subscription.
Progress is reported through the `ajaxprogress` event. Errors are reported
through `ajaxerror`/`ajaxerrorend` events before the observable errors. | | ColdObservable<AjaxResponse<R>> | |
| generateRequest | | Generates and sends the AJAX request. | | Promise<AjaxResponse<R>> | |
Methods
| Name | Privacy | Description | Parameters | Return | Inherited From |
| ----------------------- | ------- | --------------------------------------------------------------- | -------------------------- | -------- | -------------- |
| _assignAjaxRxjsConfig | private | Assigns the configuration settings for the AJAX request. | | Object | |
| _joinUrlData | private | Joins the base URL and path to create the complete request URL. | | string | |
| _joinHeaders | private | Joins the default headers with custom headers. | formData: unknown | Object | |
| _dispatchEvent | private | Dispatches a custom event with the specified type and payload. | type: string, payload: * | void | |
Exports
| Kind | Name | Declaration | Module | Package |
| ---- | ------------------- | ----------------- | ------------------------ | ------- |
| js | AjaxProviderMixin | AjaxProviderMixin | src/AjaxProviderMixin.ts | |
src/fromAjax.ts:
class: AjaxResponse
Fields
| Name | Privacy | Type | Default | Description | Inherited From |
| ----------------- | ------- | ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | -------------- |
| originalEvent | | Event | originalEvent | | |
| xhr | | XMLHttpRequest | xhr | | |
| request | | AjaxRequest | { url, headers, body, async: config.async, crossDomain: config.crossDomain, withCredentials: config.withCredentials, method: config.method, timeout: config.timeout, responseType: config.responseType as XMLHttpRequestResponseType, user: config.user, password: config.password, xsrfCookieName: config.xsrfCookieName, xsrfHeaderName: config.xsrfHeaderName, queryParams: config.queryParams, includeDownloadProgress: config.includeDownloadProgress, includeUploadProgress: config.includeUploadProgress, } | | |
| type | | string | type | | |
| status | | number | | | |
| responseType | | string | | | |
| responseHeaders | | Record<string, string> | | | |
| response | | T | getXHRResponse(xhr) | | |
| loaded | | number | | | |
| total | | number | | | |
class: AjaxError
Fields
| Name | Privacy | Type | Default | Description | Inherited From |
| -------------- | ------- | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | -------------- |
| name | | string | 'AjaxError' | | |
| xhr | | XMLHttpRequest | xhr | | |
| request | | AjaxRequest | { url, headers, body, async: config.async, crossDomain: config.crossDomain, withCredentials: config.withCredentials, method: config.method, timeout: config.timeout, responseType: config.responseType as XMLHttpRequestResponseType, user: config.user, password: config.password, xsrfCookieName: config.xsrfCookieName, xsrfHeaderName: config.xsrfHeaderName, queryParams: config.queryParams, includeDownloadProgress: config.includeDownloadProgress, includeUploadProgress: config.includeUploadProgress, } | | |
| status | | number | | | |
| responseType | | string | | | |
| response | | unknown | response | | |
class: AjaxTimeoutError
Fields
| Name | Privacy | Type | Default | Description | Inherited From |
| -------------- | ------- | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | -------------- |
| name | | string | 'AjaxTimeoutError' | | AjaxError |
| xhr | | XMLHttpRequest | xhr | | AjaxError |
| request | | AjaxRequest | { url, headers, body, async: config.async, crossDomain: config.crossDomain, withCredentials: config.withCredentials, method: config.method, timeout: config.timeout, responseType: config.responseType as XMLHttpRequestResponseType, user: config.user, password: config.password, xsrfCookieName: config.xsrfCookieName, xsrfHeaderName: config.xsrfHeaderName, queryParams: config.queryParams, includeDownloadProgress: config.includeDownloadProgress, includeUploadProgress: config.includeUploadProgress, } | | AjaxError |
| status | | number | | | AjaxError |
| responseType | | string | | | AjaxError |
| response | | unknown | response | | AjaxError |
Variables
| Name | Description | Type |
| --------- | ----------- | ------------------------ |
| url | | |
| headers | | Record<string, string> |
| body | | |
Functions
| Name | Description | Parameters | Return |
| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- | --------------------------------------- |
| fromXMLHttpRequest | Creates an `XMLHttpRequest` observable that emits `AjaxResponse` values,
replicating the behavior of the removed `rxjs/ajax` `fromAjax` factory
using the Web Platform Observable (`ColdObservable`) model. | init: AjaxConfig | ColdObservable<AjaxResponse<unknown>> |
| lastValueFrom | Resolves with the last value emitted by the observable before completion. | source$: Subscribable<T> | Promise<T> |
Exports
| Kind | Name | Declaration | Module | Package |
| ---- | -------------------- | ------------------ | --------------- | ------- |
| js | fromXMLHttpRequest | fromXMLHttpRequest | src/fromAjax.ts | |
| js | lastValueFrom | lastValueFrom | src/fromAjax.ts | |
| js | AjaxResponse | AjaxResponse | src/fromAjax.ts | |
| js | AjaxError | AjaxError | src/fromAjax.ts | |
| js | AjaxTimeoutError | AjaxTimeoutError | src/fromAjax.ts | |
src/index.ts:
Exports
| Kind | Name | Declaration | Module | Package |
| ---- | ---------------------------- | -------------------------- | ---------------------- | ------- |
| js | AjaxProvider | AjaxProvider | ./AjaxProvider.js | |
| js | AjaxProviderConfig | AjaxProviderConfig | ./AjaxProvider.js | |
| js | AjaxProviderMixin | AjaxProviderMixin | ./AjaxProviderMixin.js | |
| js | AjaxProviderMixinInterface | AjaxProviderMixinInterface | ./AjaxProviderMixin.js | |
| js | ColdObservable | ColdObservable | | rxjs |
| js | AjaxConfig | AjaxConfig | ./fromAjax.js | |
| js | AjaxResponse | AjaxResponse | ./fromAjax.js | |
| js | AjaxError | AjaxError | ./fromAjax.js | |
src/utils.ts:
Variables
| Name | Description | Type |
| ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---- |
| isStandardBrowserEnv | Determines if the code is running in a standard browser environment.
This function checks for specific conditions that indicate whether the code is
running in a standard browser environment, allowing Axios to work in various
environments like web workers, React Native, or NativeScript. | |
| isStandardBrowserWebWorkerEnv | Determines if the code is running in a standard browser WebWorker environment.
This function checks for specific conditions that indicate whether the code is
running in a standard browser WebWorker environment. It takes into account the
limitations of the `isStandardBrowserEnv` method when working with WebWorkers. | |
Functions
| Name | Description | Parameters | Return |
| ----------------- | --------------------------------------------------------------------------- | ------------------------------------- | --------- |
| isFormData | Determine if a value is a FormData | thing: * | boolean |
| assignIfDefined | Utility function to assign a property to an object if the value is defined. | obj: Object, prop: string, value: * | void |
Exports
| Kind | Name | Declaration | Module | Package |
| ---- | ------------------------------- | ----------------------------- | ------------ | ------- |
| js | isStandardBrowserEnv | isStandardBrowserEnv | src/utils.ts | |
| js | isStandardBrowserWebWorkerEnv | isStandardBrowserWebWorkerEnv | src/utils.ts | |
| js | isFormData | isFormData | src/utils.ts | |
| js | assignIfDefined | assignIfDefined | src/utils.ts | |
