Au-delà des controllers basiques
Une fois les targets et les actions simples maîtrisés, trois mécanismes changent vraiment la donne pour construire des interfaces Stimulus robustes : l'API Values, l'API Outlets et les descripteurs d'actions avancés. Ce sont eux qui évitent de retomber dans du jQuery déguisé.
L'API Values pour un état typé
Plutôt que de lire des dataset manuellement, déclarez un schéma de valeurs typées. Stimulus génère les accesseurs et déclenche automatiquement un callback changed à chaque mutation, y compris quand la valeur change côté DOM (par exemple via Turbo Streams).
// counter_controller.js
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static values = { count: { type: Number, default: 0 } }
countValueChanged(value, previousValue) {
this.element.textContent = value
}
increment() {
this.countValue++
}
}
Le typage (Number, String, Boolean, Array, Object) sérialise et désérialise automatiquement via data-counter-count-value, ce qui supprime tout parsing manuel.
Outlets : communiquer entre controllers
Les outlets permettent à un controller de référencer une instance d'un autre controller, même situé ailleurs dans le DOM, sans passer par des events custom bricolés.
static outlets = ["cart-badge"]
addItem() {
this.cartBadgeOutlets.forEach(o => o.increment())
}
La cible est déclarée via data-controller="cart-badge" et l'attribut data-order-cart-badge-outlet pointe vers un sélecteur CSS. Stimulus gère la connexion/déconnexion et expose des callbacks cartBadgeOutletConnected / Disconnected pour réagir au cycle de vie.
Descripteurs d'actions moins connus
Le format event->controller#method accepte des options souvent ignorées :
data-action="keydown.enter->form#submit"filtre directement sur une touche.click->modal#close:preventappellepreventDefault()automatiquement.scroll@window->nav#toggleattache l'écouteur surwindowplutôt que sur l'élément, utile pour éviter de gérer soi-mêmeaddEventListenerdansconnect().
Points de vigilance
- Un
changedcallback sur une Value se déclenche aussi à l'initialisation : ne pas supposer qu'il s'agit toujours d'une vraie mutation. - Les Outlets nécessitent que le controller cible soit déjà instancié dans le DOM au moment de la connexion ; en cas de rendu asynchrone (Turbo Frame), vérifier
this.hasCartBadgeOutletavant usage. - Préférer la classe API (
static classes) aux chaînes de classes en dur pour rester compatible avec des thèmes CSS variables.