Un patrón habitual y muy interesante, utilizado en ciertos frameworks, es el de la reactividad. Este patrón se basa en que, cuando suceda un cambio en una estructura de datos, la aplicación o web debe ser capaz de reaccionar y actualizar todo lo que dependa de ese valor, de modo que no tengamos que hacerlo de forma manual.
Atributos HTML sin reactividad
Por defecto, el componente que creamos no tiene reactividad.
Veamos un ejemplo para entenderlo mejor. Observa que tenemos un componente app-element
, y su atributo name
es el que tendrá el nickname del usuario. Al pulsar en el botón, cambiaremos su nick y modificaremos el atributo del componente:
<app-element>Sin nickname establecido.</app-element>
<button>Cambiar nickname</button>
<script>
const appElement = document.querySelector("app-element");
const button = document.querySelector("button");
button.addEventListener("click", () => {
const nickname = "Usuario" + Math.floor(Math.random() * 255);
appElement.setAttribute("name", nickname);
});
</script>
class AppElement extends HTMLElement {
constructor() {
super();
this.textContent = `El nickname del usuario es ${this.getAttribute("name")}.`;
}
}
customElements.define("app-element", AppElement);
Al inicio, no hay atributo name
en el elemento, por lo que muestra null
. Al pulsar en el botón, comprobarás que no ocurre nada. Sin embargo, al pulsar el botón, si que se está modificando el atributo del HTML (puedes inspeccionar el HTML y comprobarlo).
Es importante darse cuenta que el constructor()
es un método que se ejecuta cuando se crea el componente. De hecho, está modificando el contenido de texto Sin nickname establecido
por defecto y cambiandolo por otro texto. Pero al pulsar en el botón, aunque estamos cambiando su atributo name
, el componente no se da cuenta de los cambios y no se actualiza automáticamente.
Atributos HTML con reactividad
Observación de cambios
Para integrar reactividad en nuestro componente, vamos a implementar una propiedad de observación de atributos. Como hemos visto en el ejemplo anterior, por defecto los componentes no reaccionan a cambios.
Vamos a añadir la propiedad observedAttributes()
e indicar los atributos que queremos vigilar para que nos notifique automáticamente (en nuestro caso, el atributo name
):
Característica | Descripción |
---|---|
static get observedAttributes() | Indica los atributos que va a observar para notificar cuando haya cambios. |
Repasemos los cambios que hemos hecho:
- Quitamos el método
constructor()
anterior (en lugar de manual, vamos a hacerlo automático). - Añadimos el
get observedAttributes()
con un array con los atributos HTML que queremos vigilar
Es decir, en nuestro caso, el atributo name
:
class AppElement extends HTMLElement {
static get observedAttributes() {
return ["name"];
}
}
customElements.define("app-element", AppElement);
<app-element>Sin nickname establecido.</app-element>
<button>Cambiar nickname</button>
<script>
const appElement = document.querySelector("app-element");
const button = document.querySelector("button");
button.addEventListener("click", () => {
const nickname = "Usuario" + Math.floor(Math.random() * 255);
appElement.setAttribute("name", nickname);
});
</script>
Ya tenemos la observación de cambios implementada. Sin embargo, aún nos queda la detección. Cuando detecta un cambio en los atributos vigilados, dispara automáticamente el método attributeChangedCallback()
. Como aún no lo hemos implementado, no realiza ninguna tarea.
Así que vamos a implementar ese método.
Detección de cambios
Vamos a implementar el método especial attributeChangedCallback()
. Como hemos dicho, este método se llamará automáticamente al detectar un cambio en uno de los atributos HTML vigilados en el observedAttributes()
.
Observa detenidamente los parámetros de este método:
Característica | Descripción |
---|---|
attributeChangedCallback( name, old, now) | Se dispara cuando cambia un atributo observado. |
El método nos pasará por parámetro el nombre del atributo que ha cambiado en name
, así como el valor que tenía antes del cambio en old
, y el valor al que ha cambiado en now
. Los nombres de estos parámetros puedes cambiarlos a los que prefieras, lo importante es el orden.
Veamos una posible implementación de este funcionamiento:
class AppElement extends HTMLElement {
static get observedAttributes() {
return ["name"];
}
attributeChangedCallback(name, old, now) {
this.innerHTML = `Nickname cambiado de <mark>${old}</mark> a <mark>${now}</mark>.`;
}
}
customElements.define("app-element", AppElement);
<app-element>Sin nickname establecido.</app-element>
<button>Cambiar nickname</button>
<script>
const appElement = document.querySelector("app-element");
const button = document.querySelector("button");
button.addEventListener("click", () => {
const nickname = "Usuario" + Math.floor(Math.random() * 255);
appElement.setAttribute("name", nickname);
});
</script>
De esta forma, cada vez que el valor de los atributos observados de un WebComponent cambie, se ejecutará automáticamente el método attributeChangedCallback()
con los valores específicos en sus parámetros name
, old
y now
. Corre de nuestra cuenta escribir la lógica necesaria o llamar al método que queramos que actualice y renderice el HTML del componente.
Caso especial: Primer cambio
Caso especial: Primer cambio
Observa este caso en el que <app-element>
tiene un atributo name
por defecto en el HTML. Lo que ocurre es lo siguiente:
- 1️⃣ Primero se crea el componente
<app-element>
. - 2️⃣ Luego, se le añade el atributo
name
con valorPaco37
. - 3️⃣ Como el atributo
name
ha cambiado y está vigilado, salta el método. - 4️⃣ El primer cambio de
name
es denull
aPaco37
.
class AppElement extends HTMLElement {
static get observedAttributes() {
return ["name"];
}
attributeChangedCallback(name, old, now) {
this.innerHTML = `Nickname cambiado de <mark>${old}</mark> a <mark>${now}</mark>.`;
}
}
customElements.define("app-element", AppElement);
<app-element name="Paco37">Sin nickname establecido.</app-element>
<button>Cambiar nickname</button>
<script>
const appElement = document.querySelector("app-element");
const button = document.querySelector("button");
button.addEventListener("click", () => {
const nickname = "Usuario" + Math.floor(Math.random() * 255);
appElement.setAttribute("name", nickname);
});
</script>
Esta casuística podría controlarse, simplemente comprobando si el valor de old
es null
. Pero es importante tenerlo en cuenta para añadir la lógica necesaria.