Загрузочные состояния отражают период между инициацией действия и получением результата. В контексте Stimulus это особенно важно: фреймворк часто используется для постепенного обогащения HTML, асинхронных запросов, отправки форм и динамического обновления частей страницы. Отсутствие явной индикации загрузки приводит к ощущению «зависшего» интерфейса, даже если логика работает корректно.
Stimulus не навязывает готовых компонентов для индикаторов прогресса, но предоставляет достаточный набор механизмов для их реализации: контроллеры, targets, values, lifecycle-хуки и работу с событиями.
Загрузочное состояние удобно рассматривать как внутреннее состояние контроллера. Чаще всего оно выражается булевым значением:
export default class extends Controller {
static values = { loading: Boolean }
}
Такое значение становится реактивным: при изменении
loadingValue автоматически вызывается метод
loadingValueChanged. Это позволяет централизованно
управлять отображением индикаторов.
loadingValueChanged(value) {
if (value) {
this.showLoader()
} else {
this.hideLoader()
}
}
Ключевая идея — разделение логики:
Targets обеспечивают декларативную связь между DOM и контроллером. Для индикаторов обычно используются отдельные элементы:
<div data-controller="request">
<button data-action="click->request#submit">Отправить</button>
<div data-request-target="loader" hidden>
Загрузка…
</div>
</div>
export default class extends Controller {
static targets = ["loader"]
static values = { loading: Boolean }
showLoader() {
this.loaderTarget.hidden = false
}
hideLoader() {
this.loaderTarget.hidden = true
}
}
Такой подход:
querySelector;Наиболее распространённый сценарий — загрузка данных через
fetch. Загрузочное состояние устанавливается до запроса и
сбрасывается после завершения, независимо от результата.
async submit() {
this.loadingValue = true
try {
const response = await fetch("/api/data")
const data = await response.json()
this.handleSuccess(data)
} catch (error) {
this.handleError(error)
} finally {
this.loadingValue = false
}
}
Блок finally критически важен: он гарантирует корректное
завершение состояния даже при ошибках сети или исключениях в коде.
Помимо визуального индикатора, часто требуется запретить повторные действия. Это достигается тем же состоянием загрузки.
static targets = ["button"]
loadingValueChanged(value) {
this.buttonTarget.disabled = value
this.loaderTarget.hidden = !value
}
Такое поведение:
Для длительных операций индикатора «Загрузка…» недостаточно. В таких случаях используется прогресс-бар. Stimulus не управляет прогрессом напрямую, но легко связывается с источником данных о ходе выполнения.
<div data-controller="upload">
<progress max="100" data-upload-target="progress"></progress>
</div>
export default class extends Controller {
static targets = ["progress"]
updateProgress(percent) {
this.progressTarget.value = percent
}
}
Обновление может происходить:
XMLHttpRequest.upload.onprogress;Stimulus в этом случае выполняет роль слоя синхронизации между источником данных и DOM.
Иногда индикатор реализуется исключительно через CSS-анимацию. Вместо показа/скрытия элементов используется переключение классов.
loadingValueChanged(value) {
this.element.classList.toggle("is-loading", value)
}
.is-loading {
opacity: 0.6;
pointer-events: none;
}
.is-loading::after {
content: "";
/* стили спиннера */
}
Этот подход особенно удобен для:
Lifecycle-методы Stimulus позволяют автоматически управлять индикаторами при подключении и отключении контроллера.
connect() {
this.loadingValue = false
}
disconnect() {
this.cleanup()
}
Это важно при использовании:
Контроллер не должен оставлять активное состояние после удаления из DOM.
Иногда требуется единый индикатор для всего приложения. Stimulus решает эту задачу через отдельный контроллер, реагирующий на события.
document.addEventListener("request:start", () => {
this.loadingValue = true
})
document.addEventListener("request:end", () => {
this.loadingValue = false
})
Другие контроллеры генерируют эти события:
this.dispatch("start")
Такой паттерн:
При использовании Turbo навигации и форм можно реагировать на стандартные события:
turbo:submit-startturbo:submit-endturbo:before-fetch-requestturbo:before-fetch-responseconnect() {
this.element.addEventListener("turbo:submit-start", () => {
this.loadingValue = true
})
this.element.addEventListener("turbo:submit-end", () => {
this.loadingValue = false
})
}
Это позволяет внедрять индикаторы загрузки без вмешательства в серверную логику.
Смешивание логики и отображения Манипуляции DOM непосредственно в методах запросов затрудняют поддержку и повторное использование.
Отсутствие обработки ошибок Индикатор, который не скрывается при исключении, блокирует интерфейс.
Глобальные флаги без контекста Единое состояние загрузки для всех операций приводит к ложным срабатываниям.
Зависимость от таймеров Индикаторы, скрывающиеся по
setTimeout, не отражают реальное состояние системы.
Загрузочные состояния в Stimulus — не декоративный элемент, а часть архитектуры взаимодействия. Чёткое разделение:
превращает индикаторы прогресса в предсказуемый и расширяемый механизм, который одинаково хорошо работает как для локальных компонентов, так и для всего приложения.