Journal

Outlets, Values et actions avancées dans Stimulus

Journal du lundi 24 août 2026, généré le 24 août à 09:53

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:prevent appelle preventDefault() automatiquement.
  • scroll@window->nav#toggle attache l'écouteur sur window plutôt que sur l'élément, utile pour éviter de gérer soi-même addEventListener dans connect().

Points de vigilance

  • Un changed callback 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.hasCartBadgeOutlet avant 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.