Now.js Framework Documentation

Now.js Framework Documentation

ApiComponent - UI Component Wrapper

EN 04 Sep 2026 01:40

ApiComponent - UI Component Wrapper

Documentation for ApiComponent, a UI Component Wrapper for calling APIs and displaying results in HTML

📋 Table of Contents

  1. Overview
  2. Installation and Import
  3. Basic Usage
  4. HTML Attributes
  5. Configuration Options
  6. Template System
  7. Pagination
  8. Polling
  9. Events
  10. 403 / 401 Response Handling
  11. CSS State Classes
  12. JavaScript API
  13. Usage Examples
  14. API Reference
  15. 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 object

Dependencies

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 format attributeName:expression e.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 values
  • data-attr - render attributes such as src, href, or value
  • data-for - loop through data
  • data-if - conditional rendering
  • data-bind - bind complex row data into managed data-table / data-line-items elements 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.role rather than a bare role) when both levels
    genuinely describe different things.
  • The inner component renders once per ancestor load. Keep data-on-load handlers
    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

<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=15

Expected 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 ComponentManager

5. ✅ 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