Journal

Stimulus.js : Outlets, Values et cycle de vie des contrôleurs

Journal du mercredi 26 août 2026, généré le 26 août à 21:45

Stimulus.js : Outlets, Values et cycle de vie des contrôleurs

Values : au-delà des simples data-attributes

L'API values ne se contente pas de lire des chaînes : elle type et sérialise automatiquement les données selon le type déclaré (String, Number, Boolean, Array, Object). Chaque changement déclenche un callback nomValueChanged, ce qui permet de réagir à une mise à jour sans réécrire de logique de diff.

// slider_controller.js
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static values = { index: { type: Number, default: 0 } }

  indexValueChanged(value, previousValue) {
    this.element.style.setProperty("--slide", value)
  }

  next() {
    this.indexValue++
  }
}

Point clé : la valeur par défaut se déclare avec un objet { type, default }, pas juste le type nu, si vous voulez éviter les undefined côté HTML.

Outlets : communiquer entre contrôleurs sans bus d'événements

Avant les Outlets, faire parler deux contrôleurs obligeait à passer par des événements DOM ou à interroger le application global. Les static outlets résolvent ça proprement : un contrôleur déclare une dépendance vers un autre, identifié par sélecteur CSS et attribut data-*-outlet.

export default class extends Controller {
  static outlets = ["cart-item"]

  removeSelected() {
    this.cartItemOutlets.forEach(item => {
      if (item.selectedValue) item.remove()
    })
  }
}

Les callbacks cartItemOutletConnected(outlet, element) et ...Disconnected permettent de réagir dynamiquement à l'apparition ou la disparition d'un contrôleur cible, ce qui est particulièrement utile avec du contenu injecté via Turbo Streams.

Cycle de vie : ne pas confondre connect et targetConnected

  • connect() : appelé une fois que le contrôleur est attaché à l'élément et que le DOM est prêt.
  • nomTargetConnected(target) : appelé à chaque fois qu'une cible entre dans le DOM, y compris après le montage initial — essentiel pour des listes qui se remplissent de façon asynchrone.
  • disconnect() : à utiliser pour nettoyer des timers, observers ou abonnements, car Stimulus ne le fait pas pour vous.

Actions avancées

Les descripteurs d'action supportent des filtres clavier (keydown.enter->form#submit) et des options passées en paramètres via data-form-id-param, récupérables dans le handler via { params }. Cela évite de repasser par des data-* lus manuellement avec dataset.

<button data-action="click->cart#add"
        data-cart-product-id-param="42">Ajouter</button>
add({ params: { productId } }) {
  this.dispatch("added", { detail: { productId } })
}

Le couple dispatch + CustomEvent reste la bonne pratique pour notifier l'extérieur d'un contrôleur, à réserver aux cas où les Outlets seraient trop couplés.