Now.js Framework Documentation
ApiComponent - UI Component Wrapper
ApiComponent - UI Component Wrapper
Documentation for ApiComponent, a UI Component Wrapper for calling APIs and displaying results in HTML
📋 Table of Contents
- Overview
- Installation and Import
- Basic Usage
- HTML Attributes
- Configuration Options
- Template System
- Pagination
- Polling
- Events
- 403 / 401 Response Handling
- CSS State Classes
- JavaScript API
- Usage Examples
- API Reference
- Best Practices
Overview
ApiComponent is a UI Component that allows you to call APIs and display results in HTML easily without writing JavaScript yourself.
Key Features
- ✅ HTML-Based: Configure via HTML attributes
- ✅ Template Support: Support template system for rendering
- ✅ Auto-Loading: Auto-load data when creating component
- ✅ Caching: Cache data to improve performance
- ✅ Pagination: Support pagination and infinite scroll
- ✅ Polling: Auto-update data at intervals
- ✅ Event System: Emit events on changes
- ✅ Retry Logic: Auto-retry on errors
- ✅ Loading States: Handle loading, error, empty states
When to Use ApiComponent
✅ Use ApiComponent when:
- You need to display API data in HTML
- You need ease of use (no JavaScript required)
- You need pagination or infinite scroll
- You need auto-refresh data (polling)
- You need automatic loading/error states
❌ Don't use ApiComponent when:
- You need detailed rendering control (use
ApiService+ custom code) - Working with complex real-time data (use WebSocket)
- You need very complex logic
Installation and Import
ApiComponent is loaded with the Now.js Framework and is ready to use immediately via the window object:
// No import needed - ready to use immediately
console.log(window.ApiComponent); // ApiComponent objectDependencies
ApiComponent requires these dependencies:
- HttpClient or simpleFetch - For API calls
- TemplateManager - For rendering (optional)
Two Modes of Operation
ApiComponent supports 2 different modes and automatically detects which mode to use:
1. Simple Binding Mode
When: When there is NO <template> element inside the component
Purpose: Display simple data like user names, images, numbers by updating values in existing elements
Example:
<!-- Simple Binding Mode - no template -->
<div data-component="api"
data-endpoint="/api/v1/auth/me"
data-cache="true">
<img data-attr="src:data.avatar.0.url" class="avatar">
<span data-text="data.name">Loading...</span>
<span data-text="data.email">Loading...</span>
</div>Supported Attributes:
data-text- path to data for textContent (dot notation) e.g.,data-text="data.name"data-attr- set attribute value from data using formatattributeName:expressione.g.,data-attr="src:data.avatar.0.url"
Best for:
- Logged-in user info (topbar, profile card)
- Notification badges (count displays)
- Simple single-value displays
2. Template Rendering Mode
When: When there IS a <template> element inside the component
Purpose: Create new HTML from template for complex data like lists, cards, dynamic content
Example:
<!-- Template Rendering Mode - has template -->
<div data-component="api"
data-endpoint="/api/users">
<template>
<ul data-for="user of data">
<template>
<li>
<img data-attr="src:user.avatar, alt:user.name">
<span data-text="user.name"></span>
<span data-if="user.status === 'active'">Active</span>
</li>
</template>
</ul>
</template>
</div>Supported Features:
data-text- render text valuesdata-attr- render attributes such assrc,href, orvaluedata-for- loop through datadata-if- conditional renderingdata-bind- bind complex row data into manageddata-table/data-line-itemselements when needed- Other TemplateManager directives
Best for:
- Lists (users, products, posts)
- Dynamic components with multiple items
- UI requiring loops and conditions
Mode Comparison
| Feature | Simple Binding | Template Rendering |
|---|---|---|
| Template Element | ❌ No | ✅ Yes |
| Use Case | Single values | Lists/Collections |
| Complexity | Simple | Complex |
| HTML Output | Updates existing | Creates new HTML |
| Loops | ❌ | ✅ |
| Conditions | ❌ | ✅ |
| Performance | Faster | Slower (creates HTML) |
Nested Components
A component may sit inside another component. The typical reason is that one endpoint
answers something the whole page needs (the signed-in user, the permission map) while a
second answers what this particular page is about:
<main data-component="api" data-endpoint="/api/v2/session">
<div data-component="api" data-endpoint="/api/v2/users/{id}">
<h1 data-text="username"></h1>
<!-- `role` comes from the inner endpoint, `permissions` from the outer one -->
<section data-if="role === 'webadmin' && permissions['customer.manage']">
...
</section>
</div>
</main>An inner component sees its own fields plus every ancestor component's. The scope is
merged outermost-first, so the nearest component wins whenever the same field name appears
at two levels — the same order a nested scope resolves in everywhere else in the framework.
Order of arrival does not matter. The two requests run at the same time and either one
can finish first, so after a component renders it renders the components nested inside it
again, with their own merged scope. Whichever response lands last, the inner subtree always
ends up showing the inner component's answer.
Two consequences worth knowing:
- A field of the same name at two levels resolves to the inner one. Name the outer
endpoint's fields distinctly (user.rolerather than a barerole) when both levels
genuinely describe different things. - The inner component renders once per ancestor load. Keep
data-on-loadhandlers
idempotent — they run again on each of those renders.
An array payload has no field names to merge into, so a component whose endpoint answers
a bare array keeps its own data untouched and inherits nothing.
Basic Usage
1. HTML-Based (Recommended)
<div data-component="api"
data-endpoint="/api/users"
data-method="GET">
<template>
<div data-for="item in data">
<template>
<article>
<h3 data-text="item.name"></h3>
<p data-text="item.email"></p>
</article>
</template>
</div>
</template>
</div>2. JavaScript-Based
const element = document.querySelector('#my-api-component');
const instance = ApiComponent.create(element, {
endpoint: '/api/users',
method: 'GET',
autoload: true
});3. Auto-Initialization
ApiComponent will automatically create instances for elements with data-component="api":
<!-- Will be created automatically when page loads -->
<div data-component="api" data-endpoint="/api/products"></div>HTML Attributes
ApiComponent supports these attributes for configuration:
Basic Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
data-endpoint |
string | - | API endpoint URL (required) |
data-method |
string | 'GET' |
HTTP method (GET, POST, PUT, DELETE) |
data-base-url |
string | '' |
Base URL for API |
data-autoload |
boolean | true |
Auto-load data on create |
data-timeout |
number | 30000 |
Request timeout (ms) |
Cache Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
data-cache |
boolean | false |
Enable caching |
data-cache-time |
number | 60000 |
Cache duration (ms) |
data-cache applies to GET requests only. Explicit refreshes and polling bypass cache so they still fetch fresh data.
Template Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
data-template |
string | - | External template ID |
Pagination Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
data-pagination |
boolean | true |
Enable pagination |
data-page-param |
string | 'page' |
Page parameter name |
data-limit-param |
string | 'pageSize' |
Limit parameter name |
data-page-size |
number | 10 |
Items per page |
Polling Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
data-polling |
boolean | false |
Enable polling |
data-polling-interval |
number | 30000 |
Polling interval (ms) |
Event Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
data-refresh-event |
string | - | Event names to trigger refresh (comma-separated) |
Data Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
data-params |
JSON | {} |
Query parameters |
data-data |
JSON | {} |
Request body data |
data-headers |
JSON | {} |
Custom headers |
data-data-path |
string | 'data' |
Path to data in response |
data-meta-path |
string | 'meta' |
Path to meta in response |
URL Parameters Attributes
ApiComponent can automatically read URL query parameters and include them in API requests:
| Attribute | Type | Default | Description |
|---|---|---|---|
data-url-params |
boolean | false |
Enable reading URL query parameters |
data-url-params-fields |
string | - | Comma-separated field names to read from URL |
data-url-params-required |
boolean | false |
Show error if required params are missing |
Example - Email Activation Page:
<!-- URL: /activate?id=abc123 -->
<div data-component="api"
data-endpoint="api/auth/activate"
data-url-params="true"
data-url-params-fields="id"
data-url-params-required="true">
<div class="api-loading-content">Verifying...</div>
<div class="api-success-content">Email verified!</div>
<div class="api-error-content">Invalid link</div>
</div>This will automatically call api/auth/activate?id=abc123 when the component loads.
Props Attribute
You can use data-props to set all configuration in JSON format:
<div data-component="api"
data-props='{
"endpoint": "/api/users",
"method": "GET",
"cache": true,
"cacheTime": 300000,
"params": {"status": "active"}
}'>
</div>Configuration Options
When creating an instance with JavaScript, you can configure all options:
const instance = ApiComponent.create(element, {
// Basic
method: 'GET',
baseURL: '',
endpoint: '/api/users',
autoload: true,
timeout: 30000,
// Cache
cache: false,
cacheTime: 60000,
// Retry
retry: 3,
// Messages
loadingText: 'Loading...',
errorText: 'Error occurred',
emptyText: 'No data found',
// Event Handlers
onSuccess: (data, response) => {
console.log('Success:', data);
},
onError: (error) => {
console.error('Error:', error);
},
onEmpty: () => {
console.log('No data');
},
// Pagination
pagination: true,
pageParam: 'page',
limitParam: 'pageSize',
pageSize: 10,
// Polling
polling: false,
pollingInterval: 30000,
// Events
refreshEvent: 'user:updated',
// Data
params: {},
data: {},
headers: {},
// Response Paths
dataPath: 'data',
metaPath: 'meta',
// Template
template: 'my-template-id',
// Debug
debug: false
});Template System
ApiComponent uses Now.js's TemplateManager for rendering
Inline Template
<div data-component="api" data-endpoint="/api/users">
<template>
<div data-for="user in data">
<h3>{user.name}</h3>
<p>{user.email}</p>
<span data-if="user.status === 'active'">Active</span>
</div>
</template>
</div>External Template
<!-- Define template -->
<template id="user-template">
<div class="user-card">
<h3>{name}</h3>
<p>{email}</p>
</div>
</template>
<!-- Use template -->
<div data-component="api"
data-endpoint="/api/users"
data-template="user-template">
</div>Template Directives
ApiComponent supports TemplateManager directives:
data-for (Loop)
<template>
<ul>
<li data-for="item in data">
{item.name}
</li>
</ul>
</template>data-if (Conditional)
<template>
<div data-for="user in data">
<h3>{user.name}</h3>
<span data-if="user.status === 'active'" class="badge">Active</span>
<span data-if="user.status === 'inactive'" class="badge">Inactive</span>
</div>
</template>Nested Loops
<template>
<div data-for="category in data">
<h2>{category.name}</h2>
<ul>
<li data-for="product in category.products">
{product.name} - {product.price}
</li>
</ul>
</div>
</template>Pagination
ApiComponent supports server-side pagination
Basic Pagination
<div data-component="api"
data-endpoint="/api/products"
data-pagination="true"
data-page-size="20">
<template>
<div data-for="product in data">
<h3>{product.name}</h3>
<p>{product.price}</p>
</div>
</template>
<!-- Pagination Controls -->
<button onclick="this.closest('.api-component').apiInstance.loadMore()">
Load More
</button>
</div>Custom Pagination Parameters
<div data-component="api"
data-endpoint="/api/posts"
data-page-param="p"
data-limit-param="limit"
data-page-size="15">
</div>Request will be:
GET /api/posts?p=1&limit=15Expected Response Format
{
"data": [
{"id": 1, "name": "Item 1"},
{"id": 2, "name": "Item 2"}
],
"meta": {
"total": 100,
"last_page": 10,
"current_page": 1,
"per_page": 10
}
}Disable Pagination
<div data-component="api"
data-endpoint="/api/settings"
data-pagination="false">
</div>Polling
ApiComponent can poll endpoints automatically to update data
Enable Polling
<div data-component="api"
data-endpoint="/api/notifications"
data-polling="true"
data-polling-interval="10000">
<!-- Will reload data every 10 seconds -->
</div>Control Polling via JavaScript
const instance = ApiComponent.getInstance('#my-component');
// Start polling
ApiComponent.startPolling(instance);
// Stop polling
ApiComponent.stopPolling(instance);Polling Events
element.addEventListener('api:polling:start', (e) => {
console.log('Polling started');
});
element.addEventListener('api:polling:stop', (e) => {
console.log('Polling stopped');
});Events
ApiComponent emits events on changes
Event Types
| Event | When | Detail |
|---|---|---|
api:init |
Component initialized | {instance} |
api:loaded |
Data loaded successfully | {data, response, fromCache} |
api:error |
Error occurred | {error, message} |
api:empty |
No data returned | - |
api:content-rendered |
Content rendered | {data} |
api:polling:start |
Polling started | - |
api:polling:stop |
Polling stopped | - |
api:destroy |
Component destroyed | - |
Listen to Events
const element = document.querySelector('#my-api-component');
element.addEventListener('api:loaded', (e) => {
console.log('Data loaded:', e.detail.data);
console.log('From cache:', e.detail.fromCache);
});
element.addEventListener('api:error', (e) => {
console.error('Error:', e.detail.error);
});
element.addEventListener('api:empty', (e) => {
console.log('No data available');
});Refresh on Custom Event
<div data-component="api"
data-endpoint="/api/cart"
data-refresh-event="cart:updated">
<!-- Will refresh when 'cart:updated' event is emitted -->
</div>
<script>
// Anywhere in code
EventManager.emit('cart:updated');
</script>403 / 401 Response Handling
ApiComponent reacts to two HTTP status codes on its own, without any app code:
| Status | Default behavior |
|---|---|
403 Forbidden |
RouterManager.navigate() to the app's configured forbidden route |
401 Unauthorized |
RouterManager.navigate('/login') |
Configuring the forbidden route
The 403 target is not hardcoded to /403 — it reads
RouterManager.config.auth.redirects.forbidden, the same setting AuthGuard uses
for route-level permission checks, so an API-driven redirect and a route-driven
one always land on the same page. If the app never sets that config, it falls
back to /forbidden:
Now.init({
router: {
auth: {
redirects: {
forbidden: '/forbidden', // must be a route your app actually registers
unauthorized: '/login'
}
}
}
});An app that registers a route under a different path (or doesn't register
/forbidden at all) must set this explicitly — a redirect to an unregistered
route falls through to the router's own not-found handling, which replaces the
entire page content, not just the failing widget.
A hidden or removed instance never redirects
A data-component="api" element nested inside a data-if-gated container still
issues its own request the moment it's created — data-if hiding the wrapper
does not stop the nested component's own fetch (see TemplateManager's data-if
docs, "Common Pitfalls" for why). This matters most for an admin-only widget
embedded in an otherwise shared page:
<div data-if="permissions['settings.manage']">
<div data-component="api" data-endpoint="/api/v2/admin-only-widget">
...
</div>
</div>For a user without settings.manage, the outer div is removed immediately,
but the inner component's request is already in flight and will come back
403. Before acting on that response, ApiComponent checks whether the
instance is still genuinely part of the page — still tracked, and its element
still attached to document.body. If not, the 403/401 handling is skipped
entirely: no redirect, no console noise beyond the initial warning. A widget
the user was never shown has no business hijacking navigation for the whole
page over its own failed, moot request. Only a live, currently-visible
instance's 403/401 triggers the redirect.
CSS State Classes
ApiComponent automatically adds CSS classes to indicate the current state of the component. These classes use the api- prefix to avoid conflicts with common class names.
State Classes
| Class | When Applied | Description |
|---|---|---|
api-component |
Always | Added when component is initialized |
api-loading |
During data fetch | Data is being loaded from API |
api-content |
After successful load | Data loaded and rendered successfully |
api-error |
On error | An error occurred during loading |
api-empty |
When no data | API returned empty data or empty array |
Usage Example
/* Style loading state */
.api-component.api-loading {
opacity: 0.6;
pointer-events: none;
}
.api-component.api-loading::after {
content: 'Loading...';
display: block;
text-align: center;
padding: 20px;
}
/* Style error state */
.api-component.api-error {
background-color: #fee;
border: 1px solid #fcc;
padding: 20px;
}
.api-component.api-error::before {
content: '⚠️ Error loading data';
display: block;
color: #c00;
font-weight: bold;
margin-bottom: 10px;
}
/* Style empty state */
.api-component.api-empty {
text-align: center;
padding: 40px;
color: #999;
}
.api-component.api-empty::after {
content: 'No data available';
display: block;
}
/* Style content state */
.api-component.api-content {
/* Your content styles */
}Complete Example
<div data-component="api"
data-endpoint="/api/products"
class="product-list">
<template>
<div data-for="product in data" class="product-card">
<h3>{product.name}</h3>
<p>{product.price}</p>
</div>
</template>
</div>
<style>
/* Base styling */
.product-list {
min-height: 200px;
transition: all 0.3s ease;
}
/* Loading state */
.product-list.api-loading {
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: loading 1.5s ease-in-out infinite;
}
@keyframes loading {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
/* Error state */
.product-list.api-error {
background-color: #fee2e2;
border: 2px solid #ef4444;
padding: 20px;
border-radius: 8px;
}
/* Empty state */
.product-list.api-empty {
background-color: #f9fafb;
border: 2px dashed #d1d5db;
padding: 40px;
text-align: center;
color: #6b7280;
}
/* Content loaded */
.product-list.api-content {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 20px;
}
</style>Benefits of api- Prefix
The api- prefix prevents conflicts with commonly used class names:
-
❌
.loading- might conflict with your own loading classes -
❌
.error- very common class name in many projects -
❌
.content- extremely common, often used for layout -
❌
.empty- commonly used for empty states -
✅
.api-loading- specific to ApiComponent -
✅
.api-error- clear it's from ApiComponent -
✅
.api-content- won't conflict with layout classes -
✅
.api-empty- specific to API data state
JavaScript API
ApiComponent has methods for control via JavaScript
Create Instance
const instance = ApiComponent.create(element, options);Get Instance
// From element
const instance = ApiComponent.getInstance(element);
// From ID
const instance = ApiComponent.getInstance('#my-component');
// From element property
const instance = element.apiInstance;Load Data
// Load data (reset pagination)
await ApiComponent.loadData(instance);
// Load more (append data)
await ApiComponent.loadMore(instance);Refresh Data
// Refresh (reset to page 1)
ApiComponent.refresh(instance);
// Or use instance method
instance.refresh();Submit Data
// Submit data (POST)
await ApiComponent.submit(instance, {
name: 'John Doe',
email: 'john@example.com'
});Polling Control
// Start polling
ApiComponent.startPolling(instance);
// Stop polling
ApiComponent.stopPolling(instance);Cache Management
// Clear all cache
ApiComponent.clearCache();Destroy Instance
// Destroy instance and cleanup
ApiComponent.destroy(instance);Usage Examples
1. Simple Product List
<div data-component="api"
data-endpoint="/api/products"
data-cache="true"
data-cache-time="300000">
<template>
<div class="product-grid">
<div data-for="product in data" class="product-card">
<img data-src="product.image" data-alt="product.name">
<h3>{product.name}</h3>
<p class="price">{product.price} USD</p>
<button>Add to Cart</button>
</div>
</div>
</template>
</div>2. User Profile with POST
<form id="profile-form">
<input type="text" name="name" placeholder="Name">
<input type="email" name="email" placeholder="Email">
<button type="submit">Save</button>
</form>
<div id="profile-result"
data-component="api"
data-endpoint="/api/profile"
data-method="POST"
data-autoload="false">
<template>
<div class="alert success">
Profile updated: {data.name}
</div>
</template>
</div>
<script>
document.getElementById('profile-form').addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData(e.target);
const data = Object.fromEntries(formData);
const instance = ApiComponent.getInstance('#profile-result');
await ApiComponent.submit(instance, data);
});
</script>3. Infinite Scroll
<div data-component="api"
data-endpoint="/api/posts"
data-page-size="20"
id="posts">
<template>
<div data-for="post in data" class="post">
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</div>
</template>
</div>
<script>
// Infinite scroll
window.addEventListener('scroll', () => {
if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight - 500) {
const instance = ApiComponent.getInstance('#posts');
if (!instance.loading && instance.page < instance.totalPages) {
ApiComponent.loadMore(instance);
}
}
});
</script>4. Real-time Notifications (Polling)
<div data-component="api"
data-endpoint="/api/notifications"
data-polling="true"
data-polling-interval="10000"
data-cache="false">
<template>
<div class="notification-list">
<div data-for="notif in data" class="notification">
<span class="icon" data-if="notif.type === 'info'">ℹ️</span>
<span class="icon" data-if="notif.type === 'warning'">⚠️</span>
<span class="icon" data-if="notif.type === 'error'">❌</span>
<div class="content">
<h4>{notif.title}</h4>
<p>{notif.message}</p>
<time>{notif.created_at}</time>
</div>
</div>
</div>
</template>
</div>5. Search with Debounce
<input type="text" id="search-input" placeholder="Search products...">
<div data-component="api"
data-endpoint="/api/products/search"
data-autoload="false"
id="search-results">
<template>
<div data-for="product in data">
<h4>{product.name}</h4>
<p>{product.description}</p>
</div>
</template>
</div>
<script>
let searchTimeout;
const searchInput = document.getElementById('search-input');
const instance = ApiComponent.getInstance('#search-results');
searchInput.addEventListener('input', (e) => {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
instance.options.params = { q: e.target.value };
ApiComponent.refresh(instance);
}, 300);
});
</script>6. Dynamic Endpoint
<select id="category-select">
<option value="electronics">Electronics</option>
<option value="clothing">Clothing</option>
<option value="books">Books</option>
</select>
<div data-component="api"
data-endpoint="/api/products"
data-params='{"category": "electronics"}'
id="products">
<template>
<div data-for="product in data">
<h3>{product.name}</h3>
</div>
</template>
</div>
<script>
document.getElementById('category-select').addEventListener('change', (e) => {
const instance = ApiComponent.getInstance('#products');
instance.options.params.category = e.target.value;
ApiComponent.refresh(instance);
});
</script>7. Nested Data Structure
<div data-component="api"
data-endpoint="/api/categories">
<template>
<div data-for="category in data" class="category">
<h2>{category.name}</h2>
<div class="products">
<div data-for="product in category.products" class="product">
<h4>{product.name}</h4>
<p>{product.price} USD</p>
</div>
</div>
</div>
</template>
</div>8. Custom Response Path
<!-- API Response: { "success": true, "result": { "items": [...] } } -->
<div data-component="api"
data-endpoint="/api/items"
data-data-path="result.items">
<template>
<div data-for="item in data">
{item.name}
</div>
</template>
</div>9. Event-Driven Updates
<!-- Shopping Cart -->
<div data-component="api"
data-endpoint="/api/cart"
data-refresh-event="cart:updated"
id="cart">
<template>
<div>
Total Items: {data.count}
Total Price: {data.total} USD
</div>
</template>
</div>
<!-- Add to Cart Button -->
<button onclick="addToCart(123)">Add to Cart</button>
<script>
async function addToCart(productId) {
await fetch('/api/cart/add', {
method: 'POST',
body: JSON.stringify({ productId }),
headers: {'Content-Type': 'application/json'}
});
// Trigger refresh
EventManager.emit('cart:updated');
}
</script>10. Error Handling with Custom UI
<div data-component="api"
data-endpoint="/api/products"
id="products">
<template>
<div data-for="product in data">
<h3>{product.name}</h3>
</div>
</template>
</div>
<script>
const element = document.getElementById('products');
element.addEventListener('api:error', (e) => {
const errorDiv = document.createElement('div');
errorDiv.className = 'alert alert-error';
errorDiv.textContent = 'Failed to load products. ';
const retryBtn = document.createElement('button');
retryBtn.textContent = 'Retry';
retryBtn.onclick = () => {
ApiComponent.refresh(e.detail.instance);
errorDiv.remove();
};
errorDiv.appendChild(retryBtn);
element.insertBefore(errorDiv, element.firstChild);
});
</script>API Reference
Methods
// Create
create(element: HTMLElement|string, options?: object): Instance
// Get Instance
getInstance(element: HTMLElement|string): Instance|null
// Data Operations
loadData(instance: Instance, append?: boolean): Promise<void>
refresh(instance: Instance): void
loadMore(instance: Instance): void
submit(instance: Instance, data: object): Promise<void>
// Polling
startPolling(instance: Instance): void
stopPolling(instance: Instance): void
// Cache
clearCache(): void
// Lifecycle
destroy(instance: Instance): boolean
init(options?: object): Promise<ApiComponent>Instance Properties
{
id: string,
element: HTMLElement,
options: object,
data: any,
loading: boolean,
error: string|null,
timestamp: number|null,
page: number,
totalPages: number,
pageSize: number,
totalItems: number,
polling: boolean,
timer: number|null,
abortController: AbortController|null,
templateContent: string|null
}Best Practices
1. ✅ Use Data Attributes for Simple Cases
<!-- ❌ Bad: Use JavaScript when not necessary -->
<div id="products"></div>
<script>
ApiComponent.create('#products', {endpoint: '/api/products'});
</script>
<!-- ✅ Good: Use HTML attributes -->
<div data-component="api" data-endpoint="/api/products"></div>2. ✅ Enable Caching for Static Data
<!-- ✅ Good: Cache data that doesn't change often -->
<div data-component="api"
data-endpoint="/api/categories"
data-cache="true"
data-cache-time="300000">
</div>3. ✅ Use External Templates for Reusability
<!-- ✅ Good: Separate template for reusability -->
<template id="product-card">
<div class="card">
<h3>{name}</h3>
<p>{price}</p>
</div>
</template>
<div data-component="api"
data-endpoint="/api/products"
data-template="product-card">
</div>
<div data-component="api"
data-endpoint="/api/featured-products"
data-template="product-card">
</div>4. ✅ Clean Up When Removing Components
// ✅ Good: Destroy instance when no longer needed
const instance = ApiComponent.getInstance('#my-component');
ApiComponent.destroy(instance);
// Or when removing element
element.remove(); // Auto-cleanup if using ComponentManager5. ✅ Use Pagination for Large Datasets
<!-- ✅ Good: Use pagination for large datasets -->
<div data-component="api"
data-endpoint="/api/posts"
data-page-size="20">
</div>6. ✅ Handle Empty States
<div data-component="api"
data-endpoint="/api/notifications">
<template>
<div data-if="data.length > 0">
<div data-for="notif in data">
{notif.message}
</div>
</div>
<div data-if="data.length === 0">
No notifications
</div>
</template>
</div>7. ✅ Use Polling Wisely
<!-- ❌ Bad: Polling every 1 second -->
<div data-polling="true" data-polling-interval="1000"></div>
<!-- ✅ Good: Polling every 30 seconds or use WebSocket -->
<div data-polling="true" data-polling-interval="30000"></div>8. ✅ Disable Autoload When Needed
<!-- ✅ Good: Disable autoload when you need control -->
<div data-component="api"
data-endpoint="/api/search"
data-autoload="false"
id="search">
</div>
<script>
// Load when user types search
searchInput.addEventListener('input', (e) => {
const instance = ApiComponent.getInstance('#search');
instance.options.params = {q: e.target.value};
ApiComponent.refresh(instance);
});
</script>9. ✅ Use Custom Data Paths
<!-- ✅ Good: Specify path when API response isn't {data: [...]} -->
<div data-component="api"
data-endpoint="/api/legacy"
data-data-path="result.items"
data-meta-path="result.pagination">
</div>10. ✅ Listen to Events for Custom Actions
// ✅ Good: Use events for custom actions
element.addEventListener('api:loaded', (e) => {
// Update other UI elements
updateStats(e.detail.data);
// Track analytics
analytics.track('data_loaded', {
endpoint: e.detail.instance.options.endpoint
});
});Summary
When to Use ApiComponent
| Use Case | Use ApiComponent? |
|---|---|
| Display list of data from API | ✅ Yes - HTML-based |
| Need pagination | ✅ Yes - built-in support |
| Need infinite scroll | ✅ Yes - loadMore() |
| Auto-refresh data | ✅ Yes - polling support |
| Need detailed control | ❌ No - use ApiService |
| Complex real-time | ❌ No - use WebSocket |
| Form submission | ✅ Can use - submit() |
| Simple static content | ❌ Not needed |
Key Features
| Feature | Available | Note |
|---|---|---|
| HTML Attributes | ✅ | No JavaScript needed |
| Template System | ✅ | TemplateManager integration |
| Auto-Loading | ✅ | Configurable |
| Caching | ✅ | In-memory |
| Pagination | ✅ | Server-side |
| Polling | ✅ | Auto-refresh |
| Events | ✅ | Custom events |
| Retry | ✅ | Auto-retry on error |
| Loading States | ✅ | loading, error, empty |