> Manuales > Manual de Web Components

Explicamos qué es el ciclo de vida en los componentes basados en el estándar Web Components. Cómo podemos definir callbacks para ejecutar código en los distintos momentos del ciclo de vida.

Ciclo de vida en los Web Components

Los Web Components son una tecnología que ha cambiado el modo con el que se desarrollan las aplicaciones para navegadores. Ya hemos empezado a hablar sobre ellos en otras ocasiones en el Manual de Web Components, así que nos centraremos ahora en un tema específico y clave para los desarrolladores, como es su ciclo de vida.

El ciclo de vida es parte de la especificación de los Web Components y básicamente incluye diversos estados o situaciones por las que pueden pasar los Custom Elements a lo largo de su existencia en una página. Desde la instanciación de un componente, su inserción dentro del documento web, hasta su retirada del DOM, por poner varios ejemplos. Por medio de estos momentos del ciclo de vida por los que pasa cualquier componente, podemos personalizar su comportamiento en circunstancias muy concretas.

Básicamente el ciclo de vida está ahí para servir de utilidad a los desarrolladores de componentes, ya que permiten escribir código Javascript que será ejecutado cuando el componente va pasando por sus diferentes estados. Esta operación se realiza por medio de lo que se conoce como funciones "callback": funciones que son declaradas pero que no se ejecutan hasta que pasan ciertas cosas.

Estados del ciclo de vida de un componente

Para comenzar, vamos a describir los estados de un componente que definen su ciclo de vida:

Nota: Estos son los estados del ciclo de vida de los custom elements en Javascript estándar, aunque algunas librerías basadas en Web Components incorporan otros adicionales.

Ciclo de vida Custom Elements V0 vs V1

Existen dos especificaciones de Web Components, una experimental que estuvo vigente durante cierto tiempo, hasta que el estándar se estabilizó y quedó la especificación definitiva, que es la que apotaron todos los navegadores. V0 es la especificación inicial, que ahora mismo no aplica y V1 es la especificación que debemos usar.

Mencionamos esto porque el ciclo de vida de los componentes cambió de la especificación experimental (V0) a la especificación definitiva (V1), por lo que dependiendo de la antiguedad de un artículo puedes encontrar que se explica una u otra. De hecho, este artículo explicaba la especificación experimental y lo hemos actualizado ahora para cubrir la especificación definitiva.

Ciclo de vida de los componentes V1

Vamos a comenzar explicando cómo es la especificación definitiva, la que tenemos que usar en el desarrollo con Web Components. Esta espeficación contiene los métodos siguientes:

Ahora vamos a ver cómo realizar un componente que realiza acciones cuando ocurren cosas en su ciclo de vida. No importa tanto la funcionalidad del componente como apreciar cómo se va produciendo la ejecución de los métodos correspondientes a medida que hacemos cosas con el componente.

Método constructor del componente

Empezamos con algo sencillo para aclarar cuándo se ejecuta el constructor. Primero aclarar que el constructor es un método tipico de las clases de programación orientada a objetos, pero también en cierto modo forma parte del ciclo de vida del componente.

Lo relevante del constructor es que se ejecuta cuando el elemento se instancia. Esto quiere decir que, aunque el elemento solamente se haya declarado como una variable de Javascript, el constructor se habrá puesto en funcionamiento. Incluso aunque el componente no aparezca en la página por ningún lugar.

Esto lo podemos ver en el siguiente pedazo de código, donde definimos un componente con su constructor.

class TestLifecycle extends HTMLElement {
    constructor() {
        super();
        console.log('Soy el constructor');
    }
} 
customElements.define('test-lifecycle', TestLifecycle);

// Creamos un elemento basado en este custom element
document.createElement('test-lifecycle');

Al ejecutarse la línea del createElement, aunque el componente no se muestra todavía en la página, el constructor se pondrá en marcha.

Es muy importante la llamada a super() en el construtor, antes de hacer cualquier otra cosa, para que se ejecute el constructor de la clase padre.

Inserciones y eliminaciones del componente en el DOM

Ahora vamos a ver cómo podemos detectar las inserciones en el DOM de este elemento, así como el momento en el que se elimina del DOM.

class TestLifecycle extends HTMLElement {
    constructor() {
        super();
        console.log('Soy el constructor');
    }
    connectedCallback() {
        console.log('El elemento se ha insertado en el DOM');
    }
    disconnectedCallback() {
        console.log('El elemento se ha retirado del DOM');
    }
} 
customElements.define('test-lifecycle', TestLifecycle);

// Creamos un elemento
var element = document.createElement('test-lifecycle');
// Insertamos en el DOM
document.body.appendChild(element);
// Eliminamos del DOM
element.remove();

Por último vamos a aprender a realizar acciones cuando un atributo del elemento cambia. Esto incluye dos pasos importantes:

Declarar el atributo como observedAttributes

Para poder detecar cambios en los atributos, éstos tienen que ser observados. Para ello encontramos una propiedad del estándar que se llama observedAttributes.

Es una propiedad estática, que podemos crear con un getter y que debe contener un array con todos los nombres de los atributos que deben ser observados.

static get observedAttributes() { 
    return ['dia', 'mes']; 
}

Definición del método del ciclo de vida attributeChangedCallback

A continuación podemos definir ya el método attributeChangedCallback(), que se ejecutará cuando los atributos "dia" y "mes" cambien.

El método attributeChangedCallback tiene una particularidad importante y es que recibe tres parámetros:

  • El nombre del atributo que ha cambiado
  • El valor anterior del atributo antes del cambio
  • El valor al que ha cambiado.
attributeChangedCallback(name, oldValue, newValue) {
    console.log(`Ha cambiado el atributo ${name}, que tenía el valor ${oldValue} y pasa a tener el valor ${newValue}`);
}

Para acabar vamos a ver un código de un componente que implementa todos los métodos del ciclo de vida que hemos visto y su uso con Javascript para crearlo, insertarlo en el DOM, cambiar un atributo un par de veces y por último retirarlo del DOM.

class TestLifecycle extends HTMLElement {
    constructor() {
        super();
        console.log('Soy el constructor');
    }

    connectedCallback() {
        console.log('El elemento se ha insertado en el DOM');
    }
    disconnectedCallback() {
        console.log('El elemento se ha retirado del DOM');
    }

    static get observedAttributes() { 
        return ['dia']; 
    }

    attributeChangedCallback(name, oldValue, newValue) {
        console.log(`Ha cambiado el atributo ${name}, que tenía el valor ${oldValue} y pasa a tener el valor ${newValue}`);
    }
} 
customElements.define('test-lifecycle', TestLifecycle);

// Creamos un elemento
var element = document.createElement('test-lifecycle');
// Insertamos en el DOM
document.body.appendChild(element);
// Cambiamos un atributo que no existía antes
element.setAttribute('dia', 'lunes');
// Volvemos a cambiar el atributo
element.setAttribute('dia', 'martes');
// Eliminamos del DOM
element.remove();

Dado el código anterior, al ejecutarlo aparecerían los siguientes mensajes en la consola de Javascript del navegador.

Ciclo de vida en los Web Components

Ciclo de vida en web components V0

Ahora vamos a ver toda otra serie de ejemplos que son muy similares a los que hemos visto en el artículo hasta ahora, pero que pertenecen a Web Components en su especificación experimental. Básicamente es lo mismo, pero algún elemento del ciclo de vida ha cambiado de nombre.

Observarás otro cambio y es que se le están aplicando los métodos del ciclo de vida desde fuera del componente. Es perfectamente posible, ya que Javascript es muy laxo con respecto a la visibilidad y acceso a los métodos de las clases.

Asociar comportamientos a instantes del ciclo de vida de custom elements

Puedes ver todos esos estados son como si fueran eventos que ocurren durante la vida de un componente. Como a los eventos, seremos capaces de asociar funciones manejadoras, que se encargan de producir comportamientos personalizados para cada suceso. En este caso llamamos a esas funciones con el término "calback", usado en el estándar como ahora podrás ver.

Ahora veremos el código del registro de un componente en el que usaremos los diversos métodos callback del ciclo de vida. Pero para entenderlo te sugerimos la lectura del artículo del estándar de los Custom Elements, en el que se explicaron ya muchas cosas que aquí vamos a dar por sabidas.

// Creo un nuevo objeto para registrar un componente, generando un nuevo prototipo
  var elemento = Object.create(HTMLElement.prototype);

  // Defino una función callback para el instante del ciclo de vida "created"
  elemento.createdCallback = function() {
    console.log('se ha creado un elemento');
  };

  // Defino una función callback para el instante del ciclo de vida "attached"
  elemento.attachedCallback = function() {
    console.log('se ha añadido un elemento al DOM');
  };

  // Defino una función callback para el instante del ciclo de vida "detached
  elemento.detachedCallback = function() {
    console.log('se ha retirado un elemento del DOM');
  };

  // Defino una función callback para el instante del ciclo de vida "attributeCanged"
  elemento.attributeChangedCallback = function(attr, oldVal, newVal) {
    console.log('Cambiado ', attr, ' al valor: ', newVal);
  };

  // Este es el código para registrar el componente, en el que indicamos su prototipo que acabamos de definir
  document.registerElement('ciclo-de-vida', {
      prototype: elemento
  });

Apreciando los estados del ciclo de vida

Solo con que uses un elemento, colocando la etiqueta HTML 'ciclo-de-vida' éste se generaría y se adjuntaría al DOM, con lo que ya se pondrán en marcha los métodos del ciclo de vida, con sus correspondientes console.log().

<ciclo-de-vida></ciclo-de-vida>
Nota: Obviamente, para que ese elemento funcione debe conocerse previamente el elemento, por lo que el script para registrarlo visto en el punto anterior debería aparecer antes en el código HTML. (Mira al final el código completo del ejercicio).

Quizás con nuestro ejemplo te sorprenda que no observarás cambios en la página, porque el elemento del ejemplo no tiene template, pero sí deberías ver los mensajes si abres la consola Javascript.

  • se ha creado un elemento
  • se ha añadido un elemento al DOM

Para ver otros métodos del ciclo de vida necesitas el código de algunas funciones Javascript de manipulación del DOM. Para ello hemos colocado tres botones que invocan tres manipulaciones diferentes sobre el DOM, que provocarán nuevos mensajes a la consola de Javascript.

<button id="cambiaAtr">Cambia Atributo</button>
<button id="quitarDOM">quitar el elemento del DOM</button>
<button id="crearElemento">crear un elemento,  solo en memoria</button>

Esos eran los tres botones, a los que les colocamos tres manejadores de eventos para realizar cosas con elementos:

document.getElementById('cambiaAtr').addEventListener('click', function() {
  document.querySelector('ciclo-de-vida').setAttribute('data-test', 'test-value');
});
document.getElementById('quitarDOM').addEventListener('click', function() {
  document.body.removeChild(document.querySelector('ciclo-de-vida'));
});
document.getElementById('crearElemento').addEventListener('click', function() {
  document.createElement('ciclo-de-vida');
});

Código completo con el estándar Web Components V0

Eso es todo lo que necesitas para practicar con los mecanismos del ciclo de vida de los custom elements. Ahora para aclarar posibles dudas dejamos el código completo del ejercicio.

Nota: Recuerda que, a pesar que esto sea todo Javascript nativo, solo funcionará para los navegadores que ya implementan el estándar de los Web Components. Para navegadores que aún no lo tienen disponible simplemente habría que usar el correspondiente polyfill, del que ya hemos hablado anteriormente en este manual.
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Test del ciclo de vida de custom elements</title>
  <script>
  (function() {
  // Creo un nuevo objeto para registrar un componente, generando un nuevo prototipo
  var elemento = Object.create(HTMLElement.prototype);

  // Defino una función callback para el instante del ciclo de vida "created"
  elemento.createdCallback = function() {
    console.log('se ha creado un elemento');
  };

  // Defino una función callback para el instante del ciclo de vida "attached"
  elemento.attachedCallback = function() {
    console.log('se ha añadido un elemento al DOM');
  };

  // Defino una función callback para el instante del ciclo de vida "detached
  elemento.detachedCallback = function() {
    console.log('se ha retirado un elemento del DOM');
  };

  // Defino una función callback para el instante del ciclo de vida "attributeCanged"
  elemento.attributeChangedCallback = function(attr, oldVal, newVal) {
    console.log('Cambiado ', attr, ' al valor: ', newVal);
  };

  // Este es el código para registrar el componente, en el que indicamos su prototipo que acabamos de definir
  document.registerElement('ciclo-de-vida', {
      prototype: elemento
  });
  }());
  </script>
</head>
<body>
  <ciclo-de-vida></ciclo-de-vida>
<button id="cambiaAtr">Cambia Atributo</button>
<button id="quitarDOM">quitar el elemento del DOM</button>
<button id="crearElemento">crear un elemento,  solo en memoria</button>

  <script>
  window.onload = function() {
  document.getElementById('cambiaAtr').addEventListener('click', function() {
    document.querySelector('ciclo-de-vida').setAttribute('data-test', 'test-value');
  });
  document.getElementById('quitarDOM').addEventListener('click', function() {
    document.body.removeChild(document.querySelector('ciclo-de-vida'));
  });
  document.getElementById('crearElemento').addEventListener('click', function() {
    document.createElement('ciclo-de-vida');
  });
  }
  </script>
</body>
</html>

Miguel Angel Alvarez

Fundador de DesarrolloWeb.com y la plataforma de formación online EscuelaIT. Com...

Manual