Componentes Web: todo lo que necesitas saber

Compartir esta publicación

Actualmente, y desde hace ya bastante tiempo, la mayoría de los desarrollos se hacen bajo el paraguas de un framework. Si nos centramos en el front-end y Javascript, podemos encontrar decenas de frameworks. Es un reto reutilizar elementos gráficos de la interfaz como botones u otros componentes cuando se quiere porque cada uno de ellos tiene cualidades distintivas.

¿Qué son los Componentes Web?

Los Componentes Web o en inglés Web Components, son un conjunto de elementos de diferentes tecnologías estandarizadas, como HTML, CSS y Javascript, que forman una estructura que permite utilizarlos en otros sitios web o aplicaciones. Estas tecnologías permiten crear elementos personalizados tanto en funcionalidad como en apariencia. Uno de sus puntos fuertes es que son agnósticas al framework, por lo que pueden utilizarse en cualquier framework Javascript. Esto permite disponer de una librería de componentes compartidos a la vez que se dispone de varias plataformas y tecnologías. Puede ser muy útil para unificar y mantener una imagen de marca de forma más sencilla.

El Consorcio World Wide Web (W3C), también conocido como la organización que fundó internet tal y como lo conocemos hoy, desarrolló este método en 2012 para estandarizar todas las tecnologías fundacionales de la web.

¿Por qué usar Componentes Web?

La mayor parte del desarrollo web se realiza bajo un framework Javascript como Angular, Vue, o librerías tan conocidas como React JS. Todos estos frameworks y librerías son muy útiles para los desarrolladores, ya que les proporcionan una serie de herramientas que hacen que el desarrollo sea rápido y fiable.

Sin embargo, no todo son buenas noticias, porque con frecuencia los desarrolladores necesitan utilizar los mismos componentes en distintos proyectos con marcos de trabajo o bibliotecas diferentes. En consecuencia, se ven obligados a reescribir esas partes con código duplicado. Esto plantea una dificultad en términos de mantenibilidad, ya que el desarrollador debe realizar estos cambios tantas veces como se haya replicado el componente para abordar posibles problemas e integrar nuevas funcionalidades o ajustes.

Esto puede resolverse mediante el uso de Componentes Web, que permiten el desarrollo de componentes individualizados utilizando HTML, CSS y Javascript que no dependen de frameworks o librerías. En otras palabras, esto significa que el desarrollador solo tiene que construirlos una vez para utilizarlos en todos los proyectos.

Además, hay otro caso en el que el uso de Componentes Web puede ser muy interesante. En el caso de una empresa con una imagen corporativa muy marcada pero que tenga diferentes plataformas o herramientas web puede ser muy difícil unificar el estilo de botones o elementos con el mismo diseño. Con este sistema se puede tener un catálogo de componentes común que permite a los diseñadores crear un conjunto de elementos con la imagen corporativa y los desarrolladores solo tendrán que implementar una sola vez. De esta manera resulta mucho más fácil usar los mismo componentes en todas las aplicaciones y crece muchísimo la mantenibilidad de estos.

Especificaciones de los Componentes Web

Los Componentes Web están basados en 4 especificaciones principales, explicadas a continuación:

Custom Elements

Los Custom Elements, son un conjunto de APIs que permiten al desarrollador crear nuevas etiquetas HTML. Se puede definir el comportamiento y cómo se ha de crear a nivel visual.

  • Autonomous custom elements: used to create completely new HTML elements.
  • Customized built-in element: utilizado para ampliar elementos HTML existentes u otros componentes web.

Shadow DOM

La API shadow DOM permite aislar fragmentos del DOM original, de forma que se pueden ocultar aquellos elementos internos que componen un elemento mayor mostrado en el DOM. El comportamiento interno es similar al de un iframe, que permite aislar su contenido del resto del documento, pero tiene una diferencia: con el shadow DOM se mantiene el control total sobre el contenido interno. Este proceso de aislar los elementos de su entorno se denomina encapsulación y evita que el código CSS y JavaScript se filtre a otros elementos personalizados.  

Módulos ES

Antes de que existieran los módulos ES, Javascript no tenía un sistema de módulos como otros lenguajes. Para inyectar código Javascript en las aplicaciones, se utilizaban etiquetas como <script/> y más tarde aparecieron otras formas de definir módulos, como CommonJS, pero ninguna de ellas se estandarizó.

Los módulos ES aparecieron para proporcionar una solución estándar a este problema. Ahora lo tenemos incluido en Javascript ES6, y nos permite agrupar algunas funcionalidades en una biblioteca y reutilizarlas en otros archivos Javascript.

Plantillas HTML

Estas plantillas HTML permiten crear fragmentos de código reutilizables como HTML pero que no se renderizan de forma inmediata en la carga de la página. Las plantillas pueden ser insertadas en tiempo de ejecución en el documento principal usando Javascript y los recursos internos solo se ejecutan en el momento en que se insertan los elementos en el documento. Además, no importa las veces que se usa una plantilla, ya que solo se lee una vez, así que el buen rendimiento está asegurado.

Este sistema crea inicialmente una plantilla vacía para que no interfiera con el resto de la aplicación y sólo renderiza el contenido de esa plantilla cuando es necesario, garantizando así de nuevo un buen rendimiento.

Compatibilidad

La compatibilidad de los Componentes Web es muy amplia. Todos los navegadores Evergreen (Chrome, Firefox y Edge) lo soportan sin problemas. Tienen compatibilidad con todas las API (elementos personalizados, plantillas HTML, shadow DOM y módulos ES).

Aunque la compatibilidad es muy amplia hay algunas excepciones como Internet Explorer y Safari. En el caso de Internet Explorer la incompatibilidad viene dada por su cierre por parte de Microsoft que quitara el acceso a partir del 14 de Febrero de 2023. En cuanto a Safari, hay ciertas funcionalidades que son compatibles y otras que no. Los Autonomous Custom elements que han sido explicados con anterioridad son 100% compatibles en Safari, pero el Shadow DOM aún no se ha implementado y los Customized Built-in elements, después de un debate entre ingenieros de Google y Apple en 2013, decidieron que nunca se iban a implementar.

Retos de los Componentes Web

Los Componentes Web también se han enfrentado a diferentes retos para poder tener su lugar y que valga la pena su implementación. Aunque han evolucionado mucho y han mejorado hay ciertos puntos a mejorar aún.

Integración con estilos generales

Uno de los retos a los que se ha enfrentado los Componentes Web, y que por ahora tiene soluciones un tanto complejas, es el cómo manejar la sobreescritura de estilos generales de la aplicación. Para esto hay varias opciones:

  • No usar Shadow DOM: Puedes añadir directamente los estilos al custom element, aunque esto deja al descubierto el código para que algún script de forma accidental o maliciosa lo cambie.
  • Usar la clase :host: Esta clase permite seleccionar un custom element del shadow DOM y estilarlo de forma específica.  
  • Usar las custom properties de CSS: Las custom properties o variables se conectan en cascada en los Componentes Web, por lo tanto si tu elemento usa una variable esta la puedes definir dentro de :root y se podrá usar sin problema.  
  • Usar las shadow parts: Con el nuevo selector :part puedes acceder a una parte de un shadow tree. Este nuevo método te permite dar estilo a una parte concreta de custom element.  
  • Pasar los estilos como string: Se pueden pasar los estilos como parámetro para poder aplicarlos dentro del bloque <style>  

Integración con formularios

Todos los elementos de tipo <input>, <textarea> or <select> en el Shadow DOM no se vinculan automáticamente al formulario que los contiene. Inicialmente se añadían campos ocultos al DOM pero eso rompía la encapsulación del Componente Web.  

Actualmente con la nueva interfaz ElementInternals permite conectarse al formulario con valores personalizados e incluso definir validaciones. Está implementado en Chrome pero existe un polyfill disponible para los otros navegadores.  

Para demostrar cómo funciona esta nueva interfaz vamos a crear un componente básico de un formulario. Primeramente la clase ha de tener un valor estático llamado formAssociated, qué determina si el formulario está conectado o no. También se puede añadir un callback para ver en qué momento se conecta.  

class InputPwd extends HTMLElement {

  static formAssociated = true;

  formAssociatedCallback(form) {
    console.log('form is associated:', form.id);
  }
}

A continuación, en el constructor se llama al método attachInternals() que permite al componente la comunicación con el formulario u otros elementos que requieran de visibilidad sobre el valor o la validación. También se implementa el método setValue a través del cual se fija el valor en el formulario. Inicializado a un string vacío.  

constructor() {
  super();
  this.internals = this.attachInternals();
  this.setValue('');
}

setValue(v) {
  this.value = v;
  this.internals.setFormValue(v);
}

Una vez hecho esto se puede crear el método connectedCallback(), este crea un Shadow DOM y monitoriza los cambios para poder propagarlos al formulario padre.  

connectedCallback() {
  const shadow = this.attachShadow({ mode: 'closed' });

  shadow.innerHTML = `
    <style>input { width: 8em; }</style>
    <input placeholder="Password" />`;

  // subscribe for changes
  shadow.querySelector('input').addEventListener('input', e => {
   this.setValue(e.target.value);
  });
}

A partir de este punto ya se puede crear el formulario HTML que contendrá el Componente Web.

<form id="globalForm">

  <input type="text" name="user-email" placeholder="email" />

  <input-pwd name="user-pwd"></input-pwd>

  <button>Login</button>

</form>

Inyección de dependencias

La inyección de dependencias es otro reto al cual se han de afrontar los Componentes Web. En muchos casos los desarrolladores van a necesitar la inyección de dependencias mientras desarrollan para poder hacer sus componentes reutilizables.

Por este motivo se podría intentar hacer la inyección de dependencias a través del constructor como se ilustra a continuación.

class MyWebComponent extends HTMLElement { 
 constructor(logger: Logger, translations: TranslationService) {                        
  supper();
  this.logger = logger; this.translations = translations; } 
}

Desafortunadamente, esto no es válido porque por especificación cuando un elemento se convierte en Custom element se llama a su constructor sin argumentos, por tanto no se podrían pasar las dependencias. Incluso de esta manera se podría llegar a solucionar de alguna manera alternativa, pero solo funcionaria si creas esos componentes vía Javascript y no directamente en el HTML. En un caso real no tendría sentido ya que lo que se quiere es añadir los Componentes Web via HTML.

Además hay otro problema y es que en caso de que el constructor se llamará, el componente no se inserta en el DOM directamente. Por tanto, si se necesita una inyección de dependencias jerárquica sería necesario esperar hasta que se ejecute el callback connectedCallback que indica que ya está posicionado en el DOM.   

Por suerte, hay una solución sobre este problema. Para poder solucionarlo nos hemos de centrar en el método connectedCallback, ya que a partir de ese momento sabemos que tenemos la jerarquía correcta para poder inyectar las dependencias. Para poder conseguir las dependencias se puede usar el sistema de eventos integrado en el navegador. Por suerte, son síncronos. Gracias a estos eventos podemos solicitar y proveer las dependencias a través de ellos.  

Si las solicitamos desde dentro del Web component en el momento de ejecutar el método connectedCallback y las proveemos desde otro punto del código donde tengamos esas dependencias se podrá realizar la inyección sin problemas.  

Para poder hacer este proceso mucho más fácil y sencillo en el momento de la implementación hay algunas soluciones ya implementadas en forma de librería que nos aportan todo lo necesario para poder implementar la inyección de dependencias sin problema.

En la documentación de la librería WebComponents-DI se puede ver cómo se implementa al detalle con ejemplos. Aunque el funcionamiento es básicamente lo que se ha explicado antes, desde connectedCallback se solicita la dependencia y desde el componente padre se provee esa dependencia usando la infraestructura de los eventos síncronos.  

La vida de un Componente Web

La vida de un Componente Web pasa por diferentes etapas, como la definición, la construcción, la conexión a la estructura existente y la desconexión entre otras. Todos estos métodos son los lifecycles. A continuación se van a detallar todos y cada uno de ellos.

Definición del Custom Element

Para poder registrar el Custom Element se usa el método customElements.define(). Este método permite registrar un Custom Element que extiende un HTMLElement. Para poder ejecutarlo son necesarios  algunos parámetros, el primero, es el nombre, el segundo la clase que define el elemento y el tercero, es opcional, y es un objeto con opciones que permite extender un Custom Element ya existente.  

customElements.define(
 "custom-button",
 class CustomButton extends HTMLElement {
  // ...
 }
);

constructor()

En los Componentes Web el constructor es el primer método del ciclo de vida y se ejecuta una vez el Web Component ha sido inicializado. Para poder disponer de las propiedades, los eventos y métodos de la clase a la que extiende, HTMLElement, es necesario llamar a super().  

constructor() {
  super();
  this.attachShadow({ mode: "open" });
  this.shadowRoot.appendChild(template.content.cloneNode(true));
}

Una vez llamado a super también es necesario unir el nuevo elemento al Shadow DOM, si se hace como “open” se va a poder acceder con Javascript, si se hace cómo “close”, va a quedar cerrado. Una vez añadido al Shadow root se puede acceder a su contenido o incluso añadir un hijo como en el trozo de código de arriba.  

connectedCallback()

Este método será llamado cada vez que el componente sea añadido al DOM. Si por ejemplo es eliminado del DOM pero posteriormente se ha vuelto a añadir también es ejecutado. Este método sirve para tener acceso a ciertos atributos, hijos o añadir listeners.

connectedCallback() {
     this.addEventListener("click", this.onclick);
   }
   onclick() {
     console.log("clicked handled");
   }
 }

attributeChangedCallback()

Este método es usado para recibir actualizaciones sobre atributos concretos. Para esto primero hay que definir estos atributos dentro del método estático observedAttributes(). Una vez definidos se ejecutará el método attributeChangedCallback() después de cualquier modificación de los atributos. Este método tiene tres parámetros, el primero indica el nombre del atributo modificado, el segundo indica el valor antiguo y el último indica el nuevo valor. Solo se considera que se ha modificado el atributo si se ha ejecutado el método this.setAttribute().  

Los atributos se guardan como datos serializados por lo que usar getters y setters para deserializarlos puede ser de gran utilidad.

static get observedAttributes() {
  return ["disabled"];
}
attributeChangedCallback(attrName, oldVal, newVal) {
  if (attrName === "disabled") {
    this.shadowRoot.getElementById("button").disabled = newVal === "true";
  }
}
set disabled(bool) {
  this.setAttribute("disabled", bool.toString());
}
get disabled() {
  return this.getAttribute("disabled") === "true";
}

adoptedCallback()

Este método se utiliza para avisar de que el Componente Web ha sido movido de un documento a otro. Solo se ejecuta cuando el método document.adoptNode() ha sido llamado.  

adoptedCallback() {
  console.log("moved to a new document");
}

disconnectedCallback()

Este método es llamado solo cuando el Componente Web es eliminado del DOM, para notificar que ya no se mostrará más. Normalmente es usado para eliminar listeners y cancelar suscripciones.

disconnectedCallback() {
  this.removeEventListener("click", this.onclick);
}

Integración con Angular

Uno de los puntos fuertes de Web Components es la versatilidad que nos ofrece para poder integrarlos con otros frameworks y librerías. Para poder ver lo fácil que es crear un Componente Web con un framework conocido, vamos a mostrar un ejemplo sencillo de cómo construirlo con Angular.

Para poder mostrar cómo se crea un Componente Web con Angular vamos a crear un proyecto Angular con un Componente Web que será una to-do list hacer una lista muy sencilla y este Componente Web va a recibir el nombre de la todo list como input.

Como primer paso se ha de crear el proyecto y un componente donde estará la todo list. Este componente no tiene nada de especial o diferente con un componente normal de Angular. Además tal y como ya se ha dicho este componente tendrá un input que será el nombre de la todo list. Aquí está el componente en cuestión.  

Una vez el componente está creado hay que modificar el archivo app.module para permitir que exporte el componente como un Componente Web. Para ello usaremos, entre otras cosas, la librería Angular Elements, una de las muchas herramientas existentes para crear Componentes Web. A continuación se detallan todas las actuaciones que se han de hacer en este archivo.

  • Eliminamos AppComponent de Bootstrap, porque como solo vamos a usar el proyecto para exportar los Componentes Web, ya no es necesario.
  • Añadimos el componente creado todo-list como entryComponent.
  • Instalamos el paquete @angular/elements.
  • Dentro del hook ngDoBootstrap del módulo usamos el método createCustomElement para compilar el componente como un Componente Web estándar.  

Aquí puedes encontrar el archivo para poder entender mejor las modificaciones.  

Y a partir de este momento cuando se compile el proyecto en el directorio dist aparecerán los archivos llamados, runtime, main, scripts y polyfills con los que se podrá usar el Componente Web acabado de crear en cualquier otro proyecto.

Tooling

En cuanto a herramientas, los Componentes Web tampoco se quedan cortos, ya que existen varias opciones entre las que elegir. Para crear Componentes Web de forma sencilla y para que su integración y mantenimiento no sean un problema, han surgido varias librerías que ayudan al desarrollador a ser ágil en la implementación de esta tecnología.

Todas estas herramientas proporcionan principalmente un entorno que permite una mejor experiencia de desarrollo. Gran parte de estas herramientas han sido usadas por empresas importantes como Apple, Porsche o Amazon entre otras.

Stencil

Stencil.js es una herramienta creada por el equipo de Ionic con la intención de abrirse a otros frameworks ya que hasta que no apareció Stencil Ionic solo funcionaba con Angular. Gracias a Stencil, Ionic puede funcionar sin problemas con React, Vue, u otros frameworks.

Básicamente es un compilador de Componentes Web con Javascript Vanilla. Pero no solo eso, tiene algunas de las mejores cualidades de otros frameworks, como por ejemplo para optimizar los custom elements hace uso del virtual DOM como React, permite Server Side Rendering, data binding reactivo o incluso el renderizado asíncrono inspirado en React Fiber (el nuevo motor de React). Además permite usar typescript y usar JSX como motor de template y también es capaz de usar lazy loading sin webpack.

Puedes aprender más sobre esta herramienta aquí.  

Polymer

Otra herramienta conocidas para el desarrollo de Componentes Web es Polymer, que fue creada por Google, inicialmente orientada para el desarrollo de proyectos internos pero que finalmente vio la luz a nivel público. Esta librería aporta una serie de polyfills (pequeños trozos de código Javascript) que permite hacer los Componentes Webs compatibles con con la mayoría de navegadores de forma nativa.

Con esta librería podemos crear Componentes Web de forma fácil y rápida. Básicamente permiten hacer realmente compatibles con la mayoría de navegadores los Componentes Web que se desarrollan. Esto permite disponer de todos aquellos recursos de los que ya cuentan los Componentes Web sin perder funcionalidades.  

¿Quieres aprender más sobre temas técnicos? Te invito a echar un vistazo al blog de Apiumhub, cada semana se publica contenido útil sobre desarrollo frontend, desarrollo backend, arquitectura de software y mucho más.  

Author

  • Arnau Gris

    I’m a T-shaped software engineer, with knowledge in different aspects like CI/CD, Backend, Frontend. My specialization is Frontend with technologies like Angular, React, Ionic, React Native. I have nearly 5 years of experience in those fields and I’m used to work with large and small companies.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

Suscríbete a nuestro boletín de noticias

Recibe actualizaciones de los últimos descubrimientos tecnológicos

¿Tienes un proyecto desafiante?

Podemos trabajar juntos

Secured By miniOrange