Component Registry

Register named Alpine components for backend targeting and cross-component communication.

Overview

The Component Registry gives your Alpine components names, making them targetable from the backend and accessible from other components. This is essential for:

  • Backend-driven updates to specific components
  • Cross-component communication
  • Coordinated state between multiple components
  • Calling component methods from the server
Auto Cleanup
Components are automatically unregistered when their DOM elements are removed. The registry uses WeakMaps and MutationObserver for zero-memory-leak operation.

Registering Components

The x-component Directive

Add x-component to any Alpine component to register it:

<!-- Register a component named 'cart' -->
<div
    x-data="{ items: [], total: 0 }"
    x-component="cart">

    <template x-for="item in items">
        ...
    </template>

    <p>Total: $<span x-text="total"></span></p>
</div>

Component Tags

Add data-tags to group related components:

<!-- Multiple components with shared tags -->
<div
    x-data="{ items: [] }"
    x-component="cart"
    data-tags="commerce, checkout">
    ...
</div>

<div
    x-data="{ count: 0 }"
    x-component="mini-cart"
    data-tags="commerce">
    ...
</div>

The $components Magic

Access registered components from any Alpine expression using $components:

Basic Access

<!-- Get component data -->
<span x-text="$components.get('cart')?.name"></span>

<!-- Check if component exists -->
<div x-show="$components.has('cart')">Cart loaded</div>

<!-- Get components by tag -->
<span x-text="$components.getByTag('commerce').length"></span> commerce components

<!-- Get all components -->
<span x-text="$components.all().length"></span> total components

Reactive State Access

Access another component's reactive state with $components.state():

<!-- Display cart total from header -->
<header x-data>
    <span>Cart: $<span x-text="$components.state('cart')?.total || 0"></span></span>
</header>

<!-- Access specific state key -->
<div x-data>
    <span x-text="$components.state('cart', 'items')?.length || 0"></span> items
</div>

Updating State

<!-- Update another component's state -->
<button @click="$components.update('cart', { total: 99.99 })">
    Set Total
</button>

<!-- Create initial state (safe for existing props) -->
<button @click="$components.create('cart', { discount: 0 }, { onlyIfMissing: true })">
    Initialize Discount
</button>

<!-- Delete state properties -->
<button @click="$components.delete('cart', 'discount')">
    Remove Discount
</button>

Waiting for Components

When components load dynamically, use when() or onReady() to safely access them:

<!-- Promise-based waiting -->
<button @click="$components.when('cart').then(c => console.log(c))">
    Wait for Cart
</button>

<!-- With async/await -->
<button @click="async () => {
    const cart = await $components.when('cart');
    console.log('Cart ready:', cart);
}">
    Await Cart
</button>

<!-- Callback-based (fires immediately if ready) -->
<div x-data x-init="$components.onReady('cart', c => console.log('Cart:', c))">
    ...
</div>

<!-- Wait for multiple components -->
<div x-data x-init="$components.onReady(['cart', 'user'], ({ cart, user }) => {
    console.log('Both ready:', cart, user);
})">
    ...
</div>

Watching State Changes

React to state changes in other components with $components.watch():

<!-- Watch all state changes -->
<div x-data="{ cleanup: null }"
     x-init="cleanup = $components.watch('cart', (state) => {
         console.log('Cart changed:', state);
     })"
     x-destroy="cleanup?.()">
    ...
</div>

<!-- Watch specific key -->
<div x-data="{ cleanup: null }"
     x-init="cleanup = $components.watch('cart', 'total', (newTotal, oldTotal) => {
         console.log(`Total: ${oldTotal} -> ${newTotal}`);
     })"
     x-destroy="cleanup?.()">
    ...
</div>

Invoking Methods

Call methods on components using $components.invoke() or the shorthand $invoke:

<!-- The cart component -->
<div x-data="{
    items: [],
    total: 0,
    addItem(product) {
        this.items.push(product);
        this.recalculate();
    },
    recalculate() {
        this.total = this.items.reduce((sum, i) => sum + i.price, 0);
    }
}" x-component="cart">
    ...
</div>

<!-- Invoke from another component -->
<button @click="$invoke('cart', 'addItem', { name: 'Widget', price: 29.99 })">
    Add to Cart
</button>

<!-- Or using $components.invoke -->
<button @click="$components.invoke('cart', 'recalculate')">
    Recalculate
</button>

Quick Reference

Method Description
$components.get(name) Get component data by name
$components.has(name) Check if component exists
$components.getByTag(tag) Get all components with tag
$components.all() Get all registered components
$components.state(name, key?) Get reactive state proxy
$components.update(name, updates) Update component state
$components.create(name, state, opts?) Initialize state properties
$components.delete(name, keys) Remove state properties
$components.when(name, timeout?) Promise that resolves when ready
$components.onReady(name, callback) Callback when component ready
$components.watch(name, key?, cb) Watch for state changes
$invoke(name, method, ...args) Call component method

On this page