Una de las razones principales por las que nos puede interesar crear un componente en nuestra página es para modularizar una característica o funcionalidad que sabemos que se va a repetir. El componente es una estupenda forma de unir marcado (HTML), estilo (CSS) y funcionalidad (JS) en un mismo sitio, de forma que sea mucho más cómodo de mantener para nosotros, los desarrolladores.
Una de las finalidades de un WebComponent es encapsular un marcado HTML más complejo dentro de una etiqueta HTML personalizada más sencilla, simplificando mucho su utilización, reduciendo la cantidad de código que tenemos que escribir y permitiendo reutilizarlo.
Pero hay que recordar que un WebComponent, al fin y al cabo, es un fichero javascript .js
, por lo que no podemos escribir código HTML directamente, sino que tenemos que utilizar alguna de las siguientes estrategias.
En los siguientes apartados, vamos a ver las diferentes formas que tenemos de crear marcado en un componente.
Probablemente, la forma más rápida de crear marcado en nuestro componente es haciendo uso de la API Javascript que permite escribir etiquetas HTML tradicionales dentro de un
Dichas propiedades son las siguientes:
Propiedad | Descripción |
---|---|
.innerHTML |
Añade o reemplaza el marcado HTML del componente. |
.outerHTML |
Idem al anterior, pero también reemplaza el componente. Poco usado en WebComponents. |
.textContent |
Reemplaza el contenido textual del componente. No permite marcado HTML. |
.innerText |
Similar al anterior. Utilizar .textContent en su lugar. |
Las dos primeras propiedades, .innerHTML
y .outerHTML
se utilizan para reemplazar el marcado HTML de un componente, aunque .outerHTML
no es demasiado práctico en los componentes, puesto que también sustituye la propia etiqueta del componente.
Por otro lado, las dos últimas propiedades se utilizan para obtener solo el contenido textual, pero se recomienda usar .textContent
ya que .innerText
no funciona obteniendo texto de elementos de tipo
► Más información en Insertar elementos en el DOM
De las anteriores, una de las más utilizadas es .innerHTML
. Veamos un ejemplo usándolo en un componente:
class AppElement extends HTMLElement {
connectedCallback() {
this.name = this.getAttribute("name") || "Desconocido";
this.innerHTML = "<div>" + this.name + "</div>";
}
}
El método
connectedCallback()
es un método especial que se ejecuta cuando el componente se inserta en el documento HTML. Lo explicamos más adelante, en el apartado de ciclo de vida de un WebComponent.
Esto se puede refactorizar un poco para mejorar la calidad de código y hacerlo más legible. Por ejemplo, podemos sacar el atributo name
fuera del método y ponerlo en la parte superior de la clase. También podemos utilizar los backticks para indentar mejor el marcado del
Esto nos permite que si el marcado se vuelve más complejo se lea mucho mejor, pudiendo utilizar ${}
para agrupar las expresiones o variables JS, evitando también las múltiples concatenaciones:
class AppElement extends HTMLElement {
name = this.getAttribute("name") || "Desconocido";
connectedCallback() {
this.innerHTML = /* html */`
<div class="container">
<div class="name">${this.name}</div>
</div>`;
}
}
Consejo: Observa que justo antes del string template hemos añadido el comentario
/* html */
. Esto es una práctica muy utilizada en WebComponents. Con extensiones para Visual Studio Code como ES6-strings-html podemos indicarle al editor que lo que contiene elque viene a continuación es código HTML, activando el resaltado de sintaxis en color o permitiendo otras características de HTML. También lo podemos hacer con código CSS, SVG, SQL, XML o GLSL (OpenGL Shading).
Es importante tener en cuenta que si en el ejemplo anterior utilizaramos .textContent
en lugar de .innerHTML
no se renderizarían las etiquetas HTML, sino que se mostrarían literalmente, ya que .textContent
interpreta literalmente como texto el marcado HTML.
Esta forma de incluir marcado HTML dentro de strings está muy sujeta a controversia y no gusta a todos los desarrolladores. En ciertas comunidades como las de CSS-in-JS o React es muy utilizada (con considerables diferencias), pero en otras suele causar rechazo. Sin embargo, se ha convertido en la forma más extendida. Más adelante veremos otras alternativas.
Es muy posible que queramos utilizar la posibilidad de utilizar EMMET en los strings templates al igual que lo hacemos en un documento HTML, de forma que al escribir p
y pulsar TAB, nos lo reemplace por <p></p>
y posicione el cursor en su interior.
En Visual Studio Code es muy sencillo. Basta con pulsar F1 y buscar la opción Open User Settings (JSON)
. Nos aparece un fichero .json
con la configuración actual del usuario. Buscaremos o añadiremos la siguiente línea:
{
[...],
"emmet.includeLanguages": {
"javascript": "html"
},
[...]
}
Esto hará que EMMET pueda ser escrito en ficheros Javascript, algo que junto a la extensión de VSCode mencionada anteriormente, hará que escribir código HTML en WebComponents sea más sencillo y cómodo.
Otra forma de manejar el marcado HTML del componente es a través de la API del DOM de Javascript. En principio es un poco más verboso y puede resultar tedioso, pero ofrece mejor rendimiento y puede ser muy interesante si somos organizados.
Algunos de los métodos que nos puede interesar usar:
Método | Descripción |
---|---|
document.createElement( tag) |
Crea y devuelve una etiqueta HTML tag . |
element.appendChild( child) |
Añade el elemento child dentro de element . |
element.insertAdjacentHTML( pos, html) |
Inserta el código html en element . |
element.insertAdjacentElement( pos, node) |
Inserta el elemento node en element . |
En primer lugar, document.createElement()
suele utilizarse conjuntamente con .appendChild()
. El primero crea el elemento pasado por parámetro y lo devuelve, generalmente para almacenarlo en una variable. Es importante recalcar que ese elemento no se guarda en el documento HTML, aunque se haga sobre document
. Para ello, hay que utilizar el segundo de los métodos de la tabla, que lo que hace es añadir el elemento que se le pasa por parámetro en el elemento donde se llama a .appendChild()
.
Veamos un ejemplo para hacer lo mismo que el código del apartado anterior:
class AppElement extends HTMLElement {
name = this.getAttribute("name") || "Desconocido";
connectedCallback() {
const container = document.createElement("div");
container.classList.add("container");
this.appendChild(container);
const nameContainer = document.createElement("div");
nameContainer.classList.add("name");
nameContainer.textContent = this.name;
container.appendChild(nameContainer);
}
}
Como vemos, el código es algo más «verboso», pero puede ser realmente útil cuando queremos realizar bucles o una lógica dinámica que es más compleja de crear al estilo del apartado anterior, mediante cadenas de texto
Por otro lado, los métodos .insertAdjacentHTML()
o .insertAdjacentElement()
son un híbrido entre las formas de manipulación del DOM que hemos visto hasta ahora, donde tenemos más flexibilidad a la hora de insertar, ya que el parámetro pos
es un
Tienes más información de su funcionamiento en Insertar elementos en el DOM.
Otra forma interesante de trabajar con el marcado HTML en un componente es utilizando la etiqueta <template>
. Esta etiqueta HTML permite crear un fragmento de código de forma aislada del documento, de modo que todo su contenido está inerte y no es procesado ni renderizado inmediatamente por el navegador, sino que se pospondrá hasta que clonemos su contenido.
Nota: Recalcar que el navegador no procesa el contenido de una etiqueta
<template>
(ni carga sus recursos, como scripts o imágenes). Además, si se define en el HTML, su rendimiento es mayor que otras alternativas como.innerHTML
, ya que se evita todo el proceso dinámico de parseo y análisis dea marcado HTML.
Podemos utilizar esta característica para usar ese marcado como código de base que clonaremos posteriormente y utilizaremos en la creación de instancias del componente.
Veamos el mismo ejemplo anterior, utilizando esta mecánica (recuerda que la etiqueta <template>
puede definirse en el archivo .html
si se prefiere y localizar mediante un .querySelector()
):
// Marcado HTML del componente
const template = document.createElement("template");
template.innerHTML = /* html */`
<div class="container">
<div class="name"></div>
</div>`;
// Lógica Javascript del componente
class AppElement extends HTMLElement {
name = this.getAttribute("name") || "Desconocido";
connectedCallback() {
const html = template.content.cloneNode((true));
html.querySelector(".name").textContent = this.name;
this.appendChild(html);
}
}
En este ejemplo hemos colocado el marcado en la parte superior del componente (fuera de la clase). De esta forma, se creará una sola vez y se utilizará cuando sea necesario. El contenido de los métodos de clase los limitamos a trabajar con lógica de programación y gestión de datos.
Remarcar el uso de .content.cloneNode(
deep)
sobre un template. Con esto, lo que estamos haciendo es clonar el código HTML de un <template>
, para posteriormente insertarlo en nuestro componente.
En el caso de establecer
deep
atrue
, haremos una clonación profunda (deep clone), es decir, se clona el elemento y todos sus elementos hijos. En caso de establecerlo afalse
, se hará una clonación superficial (shallow clone), es decir, clonará sólo el elemento indicado (y no sus hijos, los cuales serán una referencia a los originales).
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