Система выходов (outlets)

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


Определение и регистрация outlet

Outlet — это ссылка на элемент DOM, который контроллер может использовать как свойство. Для создания outlet в HTML используется атрибут:

<div data-controller="example" data-example-target="container">
  <button data-example-target="button">Нажми меня</button>
</div>

В этом примере:

  • data-controller="example" связывает элемент с контроллером ExampleController.
  • data-example-target="button" объявляет элемент как target, который можно использовать как outlet в контроллере.

В Stimulus outlet не обязательно совпадает с target по имени, но target — это чаще всего элемент, на который контроллер будет ссылаться. Для работы с outlet нужно определить его в контроллере через метод static outlets = [...].

import { Controller } from "@hotwired/stimulus";

export default class extends Controller {
  static outlets = ["button"];

  connect() {
    console.log(this.buttonOutlet); // доступ к элементу через outlet
  }
}

Здесь this.buttonOutlet автоматически ссылается на DOM-элемент с соответствующим data-атрибутом.


Множественные и именованные outlets

Stimulus позволяет объявлять множественные элементы как один outlet, что полезно для работы с коллекциями элементов. Для этого в static outlets указывается имя и добавляется []:

static outlets = ["item[]"];

В HTML:

<div data-controller="list">
  <div data-list-outlet="item"></div>
  <div data-list-outlet="item"></div>
</div>

После подключения контроллера можно работать с массивом элементов:

this.itemOutlets.forEach(el => el.classList.add("highlight"));

Доступ к outlet и его состояние

Outlets поддерживают проверку наличия элемента через hasOutlet и hasOutlets:

if (this.hasButtonOutlet) {
  this.buttonOutlet.disabled = true;
}

if (this.hasItemOutlets) {
  console.log(`Найдено ${this.itemOutlets.length} элементов`);
}

Это позволяет безопасно обращаться к элементам, даже если они не присутствуют в DOM.


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

Outlets могут ссылаться на другие контроллеры, что облегчает взаимодействие между компонентами:

<div data-controller="parent">
  <div data-parent-outlet="child" data-controller="child"></div>
</div>

В родительском контроллере:

static outlets = ["child"];

connect() {
  this.childOutlet.someChildMethod();
}

Здесь childOutlet не просто DOM-элемент, а объект контроллера ChildController, благодаря чему можно вызывать методы дочернего контроллера напрямую.


Динамическое добавление и удаление

Stimulus автоматически отслеживает изменения DOM и обновляет outlets. Если элемент был добавлен динамически после подключения контроллера, outlet станет доступен без дополнительного кода. Аналогично, при удалении элемента outlet перестает существовать, а hasOutlet вернет false.

const newButton = document.createElement("button");
newButton.dataset.exampleOutlet = "button";
this.element.appendChild(newButton);

console.log(this.hasButtonOutlet); // true после добавления

Практические рекомендации

  1. Использовать понятные имена для outlet, отражающие роль элемента.
  2. Разделять target и outlet, если один и тот же элемент нужен для событий и как ссылка в контроллере.
  3. Использовать массивы outlet для повторяющихся элементов (например, списки, таблицы, карточки).
  4. Проверять наличие через hasOutlet перед манипуляцией, чтобы избежать ошибок в динамическом DOM.

Примеры применения

  • Кнопки управления внутри формы (submitButtonOutlet, cancelButtonOutlet).
  • Коллекция элементов списка (itemOutlets) для сортировки или подсветки.
  • Взаимодействие родительских и дочерних контроллеров через outlets.

Outlets делают структуру кода чистой и декларативной, сокращая количество поиска элементов через querySelector и повышая читаемость контроллера.