$dispatch and $watch
Components on a page are independent, but they need to talk: a form tells the list about a new task, a counter learns about a deletion. For that, $dispatch sends a custom browser event. And $watch lets you run code when a specific property changes.
$dispatch
$dispatch('name', data) creates an event on the current element. The event bubbles up the tree, and a parent catches it with a regular @name:
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
<div x-data="{ last: '' }" @task-added="last = $event.detail.title">
<button @click="$dispatch('task-added', { title: 'Buy milk' })">Add</button>
<p x-text="'Last task: ' + last"></p>
</div>After a click the page shows "Last task: Buy milk". The data you passed is in $event.detail.
Sibling components
If components are not nested, the event never bubbles to the sibling. Then listen on the whole window with the .window modifier:
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
<form x-data="{ title: '' }" @submit.prevent="$dispatch('task-added', { title }); title = ''">
<input x-model="title" placeholder="New task">
<button>Add</button>
</form>
<ul x-data="{ tasks: [] }" @task-added.window="tasks.push($event.detail.title)">
<template x-for="task in tasks">
<li x-text="task"></li>
</template>
</ul>The form and the list are two separate components. The form sends the event, the list catches it on window and adds the task. { title } is the short form of { title: title }.
$watch
$watch('property', callback) calls the function every time the property changes. The function gets the new and the old value. Watching is usually set up in init():
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
<div x-data="{
filter: 'all',
init() {
this.$watch('filter', (value, oldValue) => {
console.log('Filter:', oldValue, '→', value);
});
}
}">
<select x-model="filter">
<option value="all">All</option>
<option value="active">Active</option>
<option value="done">Done</option>
</select>
</div>Pick "Active" and the console shows "Filter: all → active". The function is not called on start, only on changes.
$watch or x-effect
$watch | x-effect | |
|---|---|---|
| What it watches | One named property | Everything read in the expression |
| Runs on start | No | Yes |
| Old value | Yes | No |
Common mistake
Naming an event in camelCase: $dispatch('taskAdded') and @taskAdded. HTML lowercases attribute names, so Alpine listens for taskadded, which nobody sends. Name events with hyphens: task-added.