Registro de Custom Elements

El almacén global de Custom Elements del navegador


Antes de escribir nuestras propias etiquetas HTML personalizadas, el navegador debe conocer la existencia de ese tipo de etiquetas. Tras crear la clase del Custom Element, debemos escribir una línea de código para asociar el nombre de la etiqueta HTML personalizada con la clase Javascript.

Dicho código suele ser algo parecido a esto:

customElements.define("app-element", AppElement);

Con este código estamos asociando la etiqueta HTML con la clase Javascript.

El registro customElements

El elemento customElements no es más que una referencia al registro de Custom Elements del navegador. Un registro donde se almacenan los tipos de etiquetas HTML personalizadas para que el navegador las reconozca.

A través de este registro global, podemos realizar varias tareas relacionadas con los custom elements:

DevuelveMétodoDescripción
.define(name,className)Asocia un custom element a una clase Javascript.
.get(name)Obtiene la clase del custom element name.
.whenDefined(name)Idem al anterior, pero en versión asíncrona.
.upgrade(node)Actualiza un custom element de forma manual.

Estos son los métodos disponibles para trabajar con el registro de custom elements. Vamos a verlos uno por uno, detalladamente con un ejemplo de uso.

Registro de Custom Elements (WebComponents)

Definir un Custom Element

Quizás, el método más utilizado de los anteriores es customElements.define(), ya que es un método mediante el cuál se puede establecer una relación entre una etiqueta HTML personalizada (custom element) y una clase con su funcionalidad, que debe extender obligatoriamente de HTMLElement (o de una clase que extienda de ella):

class AppElement extends HTMLElement {
  constructor() {
    super();
  }
}

customElements.define("app-element", AppElement);

No debemos olvidarnos de esta última línea, ya que definir la clase no es suficiente para crear la etiqueta personalizada, hay que asociar la clase con la etiqueta para que el navegador pueda entenderlas y aplicarlas correctamente.

Error: Custom Element ya usado

Uncaught DOMException: Failed to execute 'define' on 'CustomElementRegistry': the name "app-element" has already been used with this registry.

Este error parece bastante lógico y nos puede aparecer si intentamos utilizar diferentes clases para un mismo custom element. En el caso de querer dotar de múltiples funcionalidades a un mismo custom element, lo conveniente quizás sería crear una clase que herede de otras y contenga las funcionalidades buscadas, o crear diferentes custom elements.

Error: Clase ya usada

Uncaught DOMException: Failed to execute 'define' on 'CustomElementRegistry': this constructor has already been used with this registry`.

Si intentamos utilizar una misma clase para varios custom elements diferentes obtendremos este problema. En el caso de querer aplicar la misma clase en varios custom elements diferentes, nos bastaría con crear una nueva clase que extienda de la que queríamos usar, y así resolver dicha limitación.

Obtener un Custom Element

En la mayoría de los casos, sólo necesitaremos utilizar .define() para registrar los custom elements de la página actual, pero en alguna situación concreta nos puede interesar acceder a otros custom elements ya registrados de forma manual. Para ello, podemos utilizar .get():

class AppElement extends HTMLElement {
  /* ... */
}
customElements.define("app-element", AppElement);

customElements.get("app-element") === AppElement; // true

Como se puede ver, con .get() obtenemos la clase del custom element solicitado.

¿Está disponible ya?

Por otro lado, .whenDefined() se podría utilizar para avisarnos cuando un custom element ha sido definido en el registro de nuestra página. Es una versión asíncrona del método .get() para Custom Elements que aún no han sido definidos. Esto puede ser realmente útil si queremos ejecutar acciones específicas sólo cuando un custom element ha sido inicializado.

Por ejemplo, analicemos el siguiente código:

customElements.whenDefined("app-element").then((data) => {
  console.log("AppElement ha sido definido");
});

El console.log() se ejecutará cuando la página detecte que el custom element app-element ha sido definido y registrado y está disponible en el registro de la página. En data se devolverá la clase que implementa el Custom Element.

Actualización manual

Por último, tenemos el método .upgrade(), que se podría utilizar en algunos casos menos frecuentes en los que queramos actualizar un custom element de forma manual.

Normalmente, los custom elements son registrados al inicio de carga de la página y se definen de forma automática. Sin embargo, podría darse el caso en el que hemos creado una etiqueta HTML personalizada sin conectarla al DOM, y antes de haber definido el custom element. En ese caso, el elemento personalizado seguiría siendo una etiqueta HTML y no se habría actualizado aún.

Veámoslo en un ejemplo:

// 1) Creamos en memoria una etiqueta personalizada <app-element>
const element = document.createElement("app-element");

// 2) Posteriormente, definimos el custom element
class AppElement extends HTMLElement { /* ... */ }
customElements.define("app-element", AppElement);

// 3) La etiqueta aún sigue siendo una etiqueta normal
element.constructor === HTMLElement;   // true

// 4) La actualizamos manualmente (HTMLElement -> AppElement)
customElements.upgrade(element);

element.constructor === HTMLElement;   // false
element.constructor === AppElement;    // true

Estas situaciones pueden ocurrir cuando no se hace el customElements.define() inmediatamente, o en situaciones asíncronas donde se crea primero el elemento antes de tener definido y registrado el Custom Element.

¿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