Propagación de Custom Events

Manejando la propagación de eventos personalizados


Si ya hemos leído los artículos anteriores, ya deberíamos saber como emitir eventos personalizados y como propagar eventos. Sin embargo, en este artículo vamos a repasar como aplicarlo a eventos personalizados.

Pero antes de nada, recordemos los pasos a seguir:

  • 1️⃣ Crear un evento (genérico o personalizado)
  • 2️⃣ Darle un nombre y definir sus opciones (si las necesitamos).
  • 3️⃣ Si queremos, añadir información adicional al evento para enviarla.
  • 4️⃣ Emitir el evento hacia un elemento del DOM con .dispatchEvent().

Si no entiendes o no tienes clara esta parte, es muy probable que no hayas leído o te hayas saltado algún tema anterior. Sería recomendable repasarlo antes de continuar.

Detectar fase del evento

Podemos acceder a la propiedad .eventPhase de nuestro evento para conocer en la fase/modalidad de propagación en la que estamos. Esta propiedad nos devolverá un que nos indicará la fase en la que nos encontramos:

PropiedadValorDescripción
Propiedad
.eventPhaseDevuelve la fase en la que se encuentra el evento.
Valores posibles
Event.NONE0Evento no está procesándose.
Event.CAPTURING_PHASE1El evento está en fase de captura (de raíz a elemento).
Event.AT_TARGET2El evento se emitió directamente (sólo al elemento concreto).
Event.BUBBLING_PHASE3El evento está en modo burbujeo (de elemento a raíz).

Para entenderlo mejor, veamos este gráfico ilustrativo:

Propagación de eventos (bubbles / capture)

  • 1️⃣ Cuando usamos bubbles: true al crear el Custom Event permitimos la propagación de eventos, por lo que el evento se va propagando desde el elemento objetivo donde lo disparamos, hasta el elemento raíz del documento, burbujeando hacia arriba.

  • 2️⃣ Cuando usamos bubbles: true y en el .addEventListener() usamos la opción capture: true, entonces el evento se escucha sólo en la fase de captura, es decir, se ignora la fase de burbujeo, y en la vuelta desde el elemento raíz hasta el elemento donde fue disparado es donde se escuchan los eventos, invirtiendo el orden.

En el caso de que tuvieramos bubbles: false, no hay propagación de eventos, el evento no burbujea, y simplemente se disparará en el elemento del DOM indicado.

Propagación de eventos

Veamos un ejemplo de código de la propagación de eventos personalizados:

const parent = document.querySelector(".parent");
const child = document.querySelector(".child");
const baby = document.querySelector(".baby");

const options = {
  detail: { name: "ManzDev test" },
  bubbles: true
};

parent.addEventListener("test-event", (ev) => { /* ... */ }, { capture: true });
child.addEventListener("test-event", (ev) => { /* ... */ }, { capture: true });
baby.addEventListener("test-event", (ev) => { /* ... */ }, { capture: true });

const event = new CustomEvent("test-event", options);
baby.dispatchEvent(event);
  • 1️⃣ Observa que escuchamos en .parent, .child y .baby.
  • 2️⃣ En las opciones del CustomEvent tenemos bubbles: true, o sea, hay propagación.
  • 3️⃣ En las opciones del .addEventListener() tenemos capture: true, o sea, fase de captura.
  • 4️⃣ En el caso de omitir el capture: true obtendríamos fase de bubbling (burbujeo).
  • 5️⃣ En el caso de tener bubbles: false, obtendríamos fase target. Sólo el elemento disparado.

Trayectoria de propagación

Como hemos visto, los eventos se propagan a lo largo de los elementos HTML del DOM. Quizás, en algún momento necesitemos esa trayectoria o saber por qué elementos ha pasado. Para ello, tenemos unas propiedades muy interesantes:

Propiedad o MétodoDescripción
.targetIndica el elemento objetivo (donde se hizo el dispatchEvent()).
.currentTargetIndica el elemento actual donde se ha escuchado el evento.
.composedPath()Muestra el camino de elementos por donde se ha propagado el evento.

Centremonos en el fragmento de código anterior, donde creamos el evento personalizado. Si tienes dudas, una buena forma de comprobar el camino que ha seguido el evento en su propagación es ejecutando el método .composedPath(). Este nos mostrará por donde ha ido pasando el evento:

parent.addEventListener("test-event", (ev) => console.log(ev.composedPath()));
child.addEventListener("test-event", (ev) => console.log(ev.composedPath()));
baby.addEventListener("test-event", (ev) => console.log(ev.composedPath()));

// [div.baby, div.child, div.parent, body, html, document, Window]

Por otro lado, la propiedad .target nos dará el elemento desde donde se emitió el evento, <div class="baby"> en nuestro caso, y la propiedad .currentTarget nos devolverá el elemento actual en el que se escucha y procesa el evento.

Detener la propagación

Por defecto, los eventos nativos tienen la propiedad .cancelable a true. Esto significa que los eventos pueden cancelar su propagación utilizando los métodos .stopPropagation() o .stopImmediatePropagation().

Propiedad o MétodoValor por defectoDescripción
.cancelabletrueIndica si es posible cancelar el evento.
.stopPropagation()Detiene la propagación en el evento en cuestión.
.stopImmediatePropagation()Detiene la propagación en todos los eventos del mismo tipo.

Vamos a añadir un evento en el elemento intermedio .child que cancele la propagación de eventos mediante .stopPropagation(). Esta detención de la propagación de eventos en .child debería evitar que el evento llegue hasta .parent, a pesar de tener el flag bubbles activo.

const parent = document.querySelector(".parent");
const child = document.querySelector(".child");

root.addEventListener("test-event", () => console.log("Recibido en el parent."));
parent.addEventListener("test-event", () => {
  parent.stopPropagation();
  console.log("Recibido en child");
});

La diferencia de .stopPropagation() y .stopImmediatePropagation() es que este último detiene la propagación en todos los eventos de su mismo tipo, mientras que el primero sólo detiene el evento concreto en ese .addEventListener(). Recuerda que para que estos métodos funcionen, el evento debe tener el flag .cancelable a true.

¿Quién soy yo?

Soy Manz, vivo en Tenerife (España) y soy streamer partner en Twitch y profesor. Me apasiona el universo de la programación web, el diseño y desarrollo web y la tecnología en general. Aunque soy full-stack, mi pasión es el front-end, la terminal y crear cosas divertidas y locas.

Puedes encontrar más sobre mi en Manz.dev