Resources & Async Data
Avenx-JS provides built-in reactive data fetching abstractions through the <resource> Single File Component (SFC) compiler tag and the runtime Resource class (lib/core/reactive/Resource.js). Resources automatically track reactive state dependencies using an internal AvenxWatcher, trigger automatic re-fetches when dependencies change, and expose a .read() method compatible with Suspense and Error Boundaries.
SFC Compiler Tag Syntax (<resource>)
Section titled “SFC Compiler Tag Syntax (<resource>)”In Avenx Single File Components (.component.js), resources are declared at the top of component templates using <resource> tags. Avenx-JS supports two syntax formats:
1. Block Syntax
Section titled “1. Block Syntax”Use block syntax to define inline asynchronous expressions or multi-line fetch logic:
<resource name="userData"> return fetch(`/api/users/${state.userId}`).then(res => res.json());</resource>[!NOTE] For single-line expressions in block syntax, if
returnis omitted and no trailing semicolon exists (e.g.fetch('/api/data').then(r => r.json())), the Avenx compiler automatically prependsreturnand appends a semicolon.
2. Self-Closing Syntax
Section titled “2. Self-Closing Syntax”Use self-closing syntax to delegate resource fetching to a component action/method or an inline string expression:
<!-- Referencing an inline handler expression --><resource name="posts" handler="fetch('/api/posts').then(r => r.json())" />
<!-- Referencing a component action method --><resource name="profile" handler="this.fetchUserProfile" />3. Background Polling (pollInterval)
Section titled “3. Background Polling (pollInterval)”Configure automated background polling by specifying the pollInterval attribute in milliseconds on any <resource> tag:
<resource name="liveMetrics" pollInterval="5000"> return fetch('/api/metrics').then(res => res.json());</resource>Runtime Resource Class API
Section titled “Runtime Resource Class API”Under the hood, every declared resource creates an instance of the Resource class (lib/core/reactive/Resource.js).
Constructor Signature
Section titled “Constructor Signature”import { Resource } from 'avenx-core/reactive';
const resource = new Resource(name, handlerFn, componentContext, options);| Parameter | Type | Description |
|---|---|---|
name |
string |
Unique string identifier for the resource. |
handlerFn |
function(): any |
Function executing the asynchronous operation (e.g., returning a Promise). |
componentContext |
object |
The containing component instance context (this). |
options |
object |
(Optional) Configuration options object. |
Constructor Options (options)
Section titled “Constructor Options (options)”| Option | Type | Default | Description |
|---|---|---|---|
pollInterval |
number |
0 |
Interval in milliseconds for automated background data polling (disabled if 0). |
Instance Properties & States
Section titled “Instance Properties & States”Every Resource instance maintains the following public reactive properties:
interface ResourceInstance<T = any> { /** Resource identifier string */ name: string;
/** Current lifecycle status */ status: 'idle' | 'pending' | 'resolved' | 'rejected';
/** Resolved data payload (undefined while pending/rejected) */ value: T | undefined;
/** Error instance or rejection reason (undefined if resolved/pending) */ error: Error | any;
/** Active Promise instance associated with the fetch */ promise: Promise<T> | null;}Resource Status Lifecycle
Section titled “Resource Status Lifecycle” ┌──────────┐ │ idle │ └────┬─────┘ │ (fetch initiated) ▼ ┌──────────┐ │ pending │ └────┬─────┘ │ ┌─────┴────────────┐ ▼ ▼┌──────────┐ ┌──────────┐│ resolved │ │ rejected │└──────────┘ └──────────┘| Status | Value Property (resource.value) |
Error Property (resource.error) |
Promise Property (resource.promise) |
|---|---|---|---|
'idle' |
undefined |
undefined |
null |
'pending' |
undefined |
undefined |
Active Promise<any> |
'resolved' |
Resolved result T |
undefined |
Resolved Promise<T> |
'rejected' |
undefined |
Thrown Error / Reason |
Rejected Promise<any> |
Instance Methods
Section titled “Instance Methods”read()
Section titled “read()”The .read() method provides a Suspense- and Error Boundary-compatible getter:
const data = resource.read();- Pending: If
status === 'pending',read()throwsthis.promise. Suspense boundaries catch this thrown Promise to display fallback loading UI. - Rejected: If
status === 'rejected',read()throwsthis.error. Error Boundaries catch this thrown error to display fallback error UI. - Resolved: If
status === 'resolved',read()returnsthis.value.
teardown()
Section titled “teardown()”Cleans up the internal AvenxWatcher dependency tracker and clears any active background polling timer (pollTimer). This is invoked automatically when a component unmounts to prevent memory leaks and unnecessary background re-fetches:
resource.teardown();Reactive Re-fetching & Render Lifecycle
Section titled “Reactive Re-fetching & Render Lifecycle”Resource integrates directly with Avenx-JS reactivity (AvenxWatcher) and component update scheduler:
- Dependency Tracking: When a
Resourceis constructed, it wrapshandlerFnin anAvenxWatcher. Any reactive state property accessed duringhandlerFnexecution (such asstate.userIdorstate.filter) is automatically registered as a dependency. - Automatic Re-fetching: When any tracked state dependency mutates,
AvenxWatchertriggersresource.fetch(newVal)automatically. - Background Polling: When
pollIntervalis specified (> 0),ResourceinitializespollTimer(setInterval). EverypollIntervalmilliseconds, it re-evaluateshandlerFnto fetch fresh data in the background. - Teardown & Cleanup: When the component unmounts,
resource.teardown()automatically clearspollTimerviaclearIntervalalongsideAvenxWatchercleanup to prevent memory leaks. - Component Re-rendering: When the async operation resolves or rejects,
ResourcemarkscomponentContext.renderWatcher.dirty = trueand invokescomponentContext.update()to flush DOM updates.
Real-Time Polling SFC Example
Section titled “Real-Time Polling SFC Example”The following Single File Component demonstrates background polling to periodically check real-time server health every 10 seconds:
export default { template: ` <!-- Poll server status endpoint every 10 seconds --> <resource name="serverStatus" pollInterval="10000"> return fetch('/api/health').then(r => r.json()); </resource>
<div class="status-widget"> <div data-ax-show="serverStatus.status === 'pending'" class="loading"> Checking server health... </div>
<div data-ax-show="serverStatus.status === 'resolved'" class="status-badge"> Server Status: <strong>{{ serverStatus.value?.status }}</strong> (Uptime: {{ serverStatus.value?.uptime }}s) </div> </div> `,};Complete SFC Example
Section titled “Complete SFC Example”The following Single File Component demonstrates a user directory search card using a <resource> tag, state dependency tracking (state.searchQuery), status checks, and error rendering:
export default { state: { searchQuery: 'alex', },
actions: { updateQuery(event) { this.state.searchQuery = event.target.value; }, },
template: ` <resource name="searchResults"> return fetch(\`/api/users/search?q=\${state.searchQuery}\`) .then(res => { if (!res.ok) throw new Error('Search request failed'); return res.json(); }); </resource>
<div class="user-search-card"> <input type="text" data-ax-bind="searchQuery" @input="updateQuery" placeholder="Search users..." />
<!-- Pending Loading State --> <div data-ax-show="searchResults.status === 'pending'" class="spinner"> Loading user search results for "{{ state.searchQuery }}"... </div>
<!-- Rejected Error State --> <div data-ax-show="searchResults.status === 'rejected'" class="error-banner"> Error: {{ searchResults.error?.message }} </div>
<!-- Resolved Data State --> <ul data-ax-show="searchResults.status === 'resolved'" class="user-list"> <li data-ax-for="user in searchResults.value"> <strong>{{ user.name }}</strong> ({{ user.email }}) </li> </ul> </div> `,};Rendering Large Datasets with <VirtualList>
Section titled “Rendering Large Datasets with <VirtualList>”When fetching large arrays from an API (such as 1,000+ to 100,000+ items), rendering standard DOM elements for every array item with data-ax-for or <@for> can degrade browser rendering performance and increase memory usage.
To maintain 60 FPS scrolling and low memory footprint, pass the fetched resource array directly into the built-in <VirtualList> component inside <@suspense>:
<resource name="largeDataset"> return fetch('/api/large-dataset').then(r => r.json());</resource>
<@suspense> <@fallback>Loading large dataset...</@fallback>
<div style="height: 600px;"> <VirtualList :item-height="40" :items="largeDataset"> <template data-ax-as="item"> <div class="row">#{{ index + 1 }} — {{ item.name }}</div> </template> </VirtualList> </div></@suspense>[!TIP]
<VirtualList>recycles DOM nodes as the user scrolls, rendering only the rows currently visible inside the viewport. Combined with<resource>and<@suspense>, this pattern provides instant loading indicators and smooth scrolling for large API responses.