Loading data
Tasks usually live on a server, and the component has to get them after the page opens. Alpine understands asynchronous code: the init() method can be async, and await works inside it. While the data is on its way, the user sees a "Loading…" message.
A stand-in for the server
In this lesson's examples the real request is replaced by a function that returns a promise and answers after one second — like a slow server. In a real project fetch() takes its place.
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
<script>
function loadTasks() {
return new Promise(resolve => {
setTimeout(() => resolve(['Buy milk', 'Call Oleg']), 1000);
});
}
</script>
<ul x-data="{
tasks: [],
async init() {
this.tasks = await loadTasks();
}
}">
<template x-for="task in tasks">
<li x-text="task"></li>
</template>
</ul>One second after the page opens, two tasks appear. Press "Reset" in the result header to see it again.
The loading state
A loading property tells whether the request is in progress. A try...finally block makes sure it is reset in any case:
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
<script>
function loadTasks() {
return new Promise(resolve => {
setTimeout(() => resolve(['Buy milk', 'Call Oleg']), 1000);
});
}
</script>
<div x-data="{
tasks: [],
loading: true,
async init() {
try {
this.tasks = await loadTasks();
} finally {
this.loading = false;
}
}
}">
<p x-show="loading">Loading…</p>
<ul x-show="!loading">
<template x-for="task in tasks">
<li x-text="task"></li>
</template>
</ul>
</div>First you see "Loading…", and after a second the list replaces it.
Errors
A request can fail. The error is caught in catch and a message is shown:
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
<script>
function loadTasks() {
return new Promise((resolve, reject) => {
setTimeout(() => reject(new Error('Server unavailable')), 1000);
});
}
</script>
<div x-data="{ tasks: [], error: '', async init() {
try {
this.tasks = await loadTasks();
} catch (e) {
this.error = e.message;
}
} }">
<p x-show="error" x-text="'Error: ' + error"></p>
</div>After a second, "Error: Server unavailable" appears.
With a real server
In a project the loading function calls the API with fetch(), as in the "Server requests: fetch" lesson of the tutorial:
// src/api.js
export async function loadTasks() {
const response = await fetch('/api/tasks');
if (!response.ok) throw new Error('Error ' + response.status);
return response.json();
}The component does not change: it still waits for a promise with await.
Common mistake
Reading data that is not there yet. Until the promise resolves, tasks is empty and selected is null. The expression x-text="selected.title" fails before the response arrives. Give initial values in x-data — an empty array, an empty string — and hide dependent blocks behind x-if until the data is there.