¿Cómo comunicar controladores Stimulus?

Maximiliano Mendivil
Unagi
Published in
4 min readNov 1, 2023

Una de las ventajas de Stimulus es su simpleza y por eso debemos buscar que los controladores tengan una única responsabilidad para lograr que los mismos sean reutilizables.

Debido a esto, muchas veces necesitamos que ellos se comuniquen entre sí para no repetir la lógica y delegar la responsabilidad al controlador correspondiente.

La comunicación entre controladores se puede dar a través de outlets o eventos; pero puntualmente en este artículo, nos centraremos en la comunicación de controladores a través de eventos.

Comunicación de controladores a través de eventos

Los controladores de Stimulus cuentan con un método dispatch para disparar un evento manualmente. Por ejemplo:

class ClipboardController extends Controller {
static targets = [ "source" ]

copy() {
this.dispatch("copy", { detail: { content: this.sourceTarget.value } })
navigator.clipboard.writeText(this.sourceTarget.value)
}
}

Los parámetros que recibe el método dispatch son:
- nombre del evento, donde Stimulus le añade automáticamente como prefijo el nombre del controlador más dos puntos. En el caso del ejemplo, nos suscribiríamos al evento como clipboard:copy.
- detalle del evento, donde se agrega el payload dentro de la clave detail.

Caso práctico

Veamos con un ejemplo cómo funcionaría la comunicación entre dos controladores Stimulus. Para ello, vamos a armar un pequeño formulario con un único campo de texto y un botón para enviar el formulario. Por defecto, el botón enviar estará deshabilitado.

Cuando el usuario ingresa un valor, se habilita el botón de enviar.

El código para construir el formulario se ve como lo siguiente

<form data-controller="form" data-action="input:valid->form#enableSubmit input:invalid->form#disableSubmit">
<input type="text" placeholder="Ingrese su nombre" data-controller="input" data-input-target="input" data-action="input#change" />
<input type="submit" value="Enviar" data-form-target="submit" />
</form>

Para manejar esta lógica, podemos utilizar dos controladores Stimulus: el controlador FormController para manejar el estado del botón enviar y el envío del formulario y el controlador InputController para manejar el estado de validez del campo de texto.

class FormController extends Controller {
static targets = [ "submit" ]

connect() {
this.disableSubmit()
}

enableSubmit({ detail: { name } }) {
this.submitTarget.disabled = false;
this.name = name
}

disableSubmit() {
this.submitTarget.disabled = true;
}
}
class InputController extends Controller {
static targets = [ "input" ]

change() {
if (this.inputTarget.value.length > 0) {
this.dispatch("valid", { detail: { name: this.inputTarget.value } })
}
else {
this.dispatch("invalid")
}
}
}

Como vemos en el ejemplo, el controlador Input dispara un evento valid o invalid dependiendo de si el campo de texto tiene un valor ingresado o no.
Para que el controlador Form pueda tomar la acción de habilitar o deshabilitar el botón enviar, necesitamos escuchar los eventos que dispara el controlador Input. Esto lo hacemos acá:

data-action="input:valid->form#enableSubmit input:invalid->form#disableSubmit"

Lo anterior lo podemos leer de la siguiente manera: cuando el campo de texto sea válido, se debe ejecutar el método enableSubmit del controlador FormController. Caso contrario, cuando el campo de texto sea inválido, se debe ejecutar el método disableSubmit del controlador FormController.
De ésta manera pudimos comunicar dos controladores Stimulus manteniendo ambos con una única responsabilidad y con la posibilidad de poder reutilizarlos en otras vistas.

Complejizando el ejemplo

Supongamos que ahora queremos lanzar un alert cuando se envía el formulario. Para ello, agregamos un elemento al HTML que se va a encargar de escuchar el submit del formulario y mostrar el alert en pantalla

<form>

<input type="submit" value="Enviar" data-form-target="submit" data-action="form#submit" />
</form>

<div data-controller="alert" data-action="form:submit->alert#show"></div>

Agregamos el método submit al controlador Form

submit(event) {
event.preventDefault()
this.dispatch("submit", { detail: { name: this.name} })
}

Creamos el controlador Alert con el método show para mostrar un saludo en pantalla.

class AlertController extends Controller {
show({ detail: { name } }) {
alert(`Hola ${name}, un placer saludarte!`)
}
}

Hasta acá parece que está todo bien: agregamos un evento submit en el controlador Form, escuchamos el evento form:submit en el elemento donde se definió el controlador Alert, pero al hacer click en el botón enviar no se muestra ningún mensaje. ¿Por qué ocurre esto?

Por defecto, los eventos se propagan en forma ascendente en la jerarquía de elementos HTML. Como el bloque HTML asociado al controlador Alert se encuentra en el mismo nivel que el bloque HTML asociado al controlador Form, necesitamos agregar @window al evento.

<div data-controller="alert" data-action="form:submit@window->alert#show"></div>

Ahora al hacer click sobre el botón enviar veríamos algo como

Conclusión

Como vimos en los ejemplos, de esa forma es como podemos comunicar nuestros controladores Stimulus a través de eventos. Si bien existen otras alternativas que nos permiten acceder a instancias de otros controladores (como los Outlets mencionados anteriormente), decidimos dejarlo para un futuro artículo.

¿Te animás a probar esto en tus proyectos?

--

--