Коммуникация между контроллерами

Stimulus построен на концепции легковесных контроллеров, каждый из которых управляет отдельной частью DOM. В реальных приложениях часто возникает необходимость, чтобы контроллеры взаимодействовали друг с другом, обменивались данными или реагировали на события, происходящие вне их области ответственности. Stimulus предоставляет несколько эффективных способов для такой коммуникации.


Использование Custom Events

Custom Events — основной инструмент для передачи информации между контроллерами. DOM позволяет создавать события с произвольными данными, которые могут быть перехвачены другими контроллерами.

Создание и отправка события

this.element.dispatchEvent(new CustomEvent('user:updated', {
  detail: { name: 'Иван', age: 30 },
  bubbles: true
}))
  • detail содержит данные, которые передаются слушателям.
  • bubbles: true позволяет событию подниматься по DOM, чтобы другие контроллеры могли его поймать.

Обработка события в другом контроллере

import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  connect() {
    this.element.addEventListener('user:updated', this.handleUserUpdate.bind(this))
  }

  handleUserUpdate(event) {
    console.log(event.detail.name) // 'Иван'
    console.log(event.detail.age)  // 30
  }
}

Ключевой момент: использование bubbles: true позволяет контроллерам, находящимся выше в DOM, реагировать на события, не создавая прямую зависимость между ними.


Метод application.getControllerForElementAndIdentifier

Stimulus предоставляет возможность напрямую получить контроллер по элементу и идентификатору. Это полезно, когда требуется вызвать методы другого контроллера.

import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  connect() {
    const otherController = this.application.getControllerForElementAndIdentifier(
      document.querySelector("#profile"), "profile"
    )
    if (otherController) {
      otherController.updateName("Иван")
    }
  }
}

Плюсы прямого вызова:

  • Полный контроль над методами другого контроллера.
  • Прямая передача данных без промежуточных событий.

Минусы:

  • Создает жесткую зависимость между контроллерами.
  • Требует точного знания структуры DOM.

Использование Stimulus.Application для глобальных событий

Для сложных приложений удобно использовать объект application как центральный диспетчер событий.

// в initialization.js
const application = Stimulus.Application.start()
application.register("user", UserController)
application.register("notifications", NotificationsController)

// В контроллере
this.application.dispatch('user:logged-in', { userId: 123 })

Другой контроллер может подписаться на эти события:

this.application.on('user:logged-in', (event) => {
  console.log(event.userId)
})

Это позволяет:

  • Централизовать обмен сообщениями.
  • Сохранять независимость контроллеров.
  • Упрощать масштабирование приложения.

Связывание через targets и data-action

Для частого взаимодействия между близко расположенными элементами можно использовать targets и data-action.

<div data-controller="form">
  <input data-form-target="name" data-action="input->form#updateName">
</div>
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = ["name"]

  updateName() {
    const value = this.nameTarget.value
    this.element.dispatchEvent(new CustomEvent('name:changed', {
      detail: value,
      bubbles: true
    }))
  }
}

Другой контроллер может слушать событие name:changed и выполнять свои действия. Это эффективно для локальной коммуникации между компонентами на одной странице.


Передача данных через data-* атрибуты

Иногда контроллеры обмениваются состоянием через data-* атрибуты элементов. Это позволяет связывать состояние с DOM без создания дополнительных событий.

<div id="counter" data-controller="counter" data-counter-value="0"></div>
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static values = { value: Number }

  increment() {
    this.value++
    this.element.dataset.counterValue = this.value
  }
}

Другой контроллер может прочитать data-counter-value для синхронизации своего состояния.


Рекомендации по выбору подхода

  • Custom Events — идеальны для независимых контроллеров и глобальных уведомлений.
  • Прямой вызов через getControllerForElementAndIdentifier — когда нужен точный контроль и уверенность в наличии целевого контроллера.
  • Application events — для сложных приложений с централизованным управлением событиями.
  • Targets и data-action — для локального обмена между тесно связанными элементами.
  • data-* атрибуты — для передачи простого состояния через DOM без сложной логики событий.

Комбинация этих подходов позволяет создавать гибкие, масштабируемые приложения на Stimulus, где контроллеры остаются независимыми, но эффективно обмениваются данными и событиями.