Creación de Componentes Web con Custom Elements API
Resumen
¿Cómo crear componentes web en HTML utilizando Custom Elements?
Crear un componente web utilizando la API de Custom Elements es una habilidad fundamental para cualquier desarrollador frontend moderno. Comencemos con un código básico que muestre cómo construir un componente desde cero utilizando las APIs nativas del navegador, sin necesidad de librerías externas.
Introducción a Custom Elements
Custom Elements es una API web estándar que permite a los desarrolladores definir nuevas etiquetas HTML personalizadas con comportamientos propios. Lo más emocionante es que estas etiquetas se pueden reutilizar en cualquier parte de la aplicación.
Los primeros pasos para definir un componente
Para definir tu componente, necesitas tener al menos dos archivos: un archivo HTML que actuará como entorno de prueba, y un archivo JavaScript donde escribirás la lógica del componente.
¿Cómo hacer que el componente se muestre en el DOM?
Para insertar contenido visual, primero creamos elementos y luego los insertamos en el DOM mediante el método connectedCallback.
classMyElementextendsHTMLElement{constructor(){super();const p =document.createElement('p'); p.textContent='Hola Mundo y Felices';}connectedCallback(){const p =document.createElement('p'); p.textContent='Hola Mundo y Felices';this.appendChild(p);}}customElements.define('my-element',MyElement);
Ampliando el componente con contenido y estilos
Para hacer el componente más complejo, podemos agregar una plantilla HTML y CSS directamente en nuestro archivo JavaScript:
const template =document.createElement('div');template.innerHTML=`<style>p{color:blue;}.texto{color:red;}</style><pclass="texto">Texto ejemplo para la clase</p><p>Hola Mundo 2</p>`;classMyElementextendsHTMLElement{constructor(){super();}connectedCallback(){this.appendChild(template);}}customElements.define('my-element',MyElement);
¿Por qué usar Custom Elements?
Reutilización: Los componentes que creamos se pueden utilizar en múltiples lugares de nuestra aplicación.
Encapsulamiento: Nos permite encapsular la lógica y estilo del componente, evitando colisiones con otros estilos en la página.
Estandarización: No se requieren librerías externas, lo que reduce la carga de dependencias en nuestro proyecto y mejora el rendimiento.
Descubre y experimenta más allá
Dominar los Custom Elements es el primer paso hacia el moderno desarrollo web utilizando web components. En futuras lecciones, exploraremos cómo añadir soporte adicional para Shadow DOM y HTML Templates, llevando tus componentes al siguiente nivel. Experimenta, practica y adéntrate en el vasto mundo de los componentes web. ¡El futuro del desarrollo frontend está aquí para explorar!
document.createElement: Crea una nueva etiqueta en memoria
element.setAttribute: Establece un atributo a alguna etiqueta
element.getAttribute: Obtiene el atributo de una etiqueta
element.textContent: Establece el contenido en texto de una etiqueta
element.innerHTML: Establece el contenido HTML de una etiqueta
element.appendChild: Inserta esa etiqueta que estaba en memoria al DOM real
.
También recordemos que no es buna práctica usar innerHTML 👀
I disagree, en el curso de manipulación del DOM se menciona que se puede utilizar innerHTML siempre y cuando no provenga de un input del usuario 👀
Hola:
O que tengas muy en cuenta que si los datos del input provienen de lo que un usuario escribe, es super importante hacerle una limpieza (o como ahora se dice sanitizar la información que el usuario proporciona) para evitar inyección de código XSS
Saludos :)
Dejo un componente que hice para practicar que consiste en una tarjeta básica =D
const template =` <div class="card">
<h2 class="title">New Card</h3>
<p class="description">This is an example of a card description made for the Web Component Course from Platzi</p>
<small class="author">made by @nazarenoalt</small>
</div>
<style>
@import url('https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,400;1,700&display=swap');
* {
font-family: 'Open Sans', Arial, Helvetica, sans-serif;
margin: 0;
padding: 0;
}
.card {
width: 200px;
margin: 20px;
padding: 20px;
border-radius: 7px;
box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.3);
}
.title {
margin: 10px 0;
}
.description {
margin-bottom: 10px;
color: rgb(134, 134, 134);
}
</style>
`classcardElementextendsHTMLElement{constructor(){super();}connectedCallback(){document.body.innerHTML= template;}}customElements.define('card-element', cardElement);
¿Cuál es la diferencia entre document.body.innerHTML y this.appenChild()?
MUY recomendable tomar el curso de manipulacion del DOM. Por favor no seguir si no lo tomaste.
Podrías colocar el enlace al curso GRACIAS
Excelente curso, llevaba rato buscando este contenido.
Hola:
Tengo una duda.
Porque si en el minuto 13:38, aparecen 3 etiquetas de Hola mundo!!, en el minuto 13:42, no aparecen 3 veces:
hola Mundo!!
Hola mundo 2!
texto ejemplo para la case!
hola Mundo!!
Hola mundo 2!
texto ejemplo para la case!
hola Mundo!!
Hola mundo 2!
texto ejemplo para la case!
Saludos :)
pd. Puse este item aquí ya que en preguntas no me dejo poner mucho texto.
Exactamente no se lo que pasa, pero al debuggear, me di cuenta que se crea el template dentro del primer tag <my-element> pero cada vez que se agregaba un tag nuevo, este se template pasaba al ultimo hijo. Es decir se agrega al primer my-element, cuando se crea el segundo se borra del primer my-element y se agrega al segundo my-element y asi sucesivamente.
Hola @Roberto aparece 3 veces por que en el .html repitió la etiqueta <my-element> y después se cambio por un templete y solo quedo una etiqueta en el archivo .html espero te sirva.
Alguna idea de como solucionar este error? (abajo)
const template =document.createElement('div')template.innerHTML=`<style>p{color:blue;}.texto{color:red;}</style><pclass="texto">Hola mundo 2</2>
<p>Hola mundo 3, este es un texto de ejemplo</2>
`classmyElementextendsHTMLElement{constructor(){super();this.p=document.createElement('p');}connectedCallback(){this.p.textContent='Hola mundo!'this.appendChild(this.p)this.appendChild(this.template)}}customElements.define('my-element',myElement);
Hola Luis, es por el cors. Y sucede porque estás intentando cargar tu archivo directo desde tu computadora, abriéndolo en el navegador, sin usar un servidor (fijate en la url del profesor y mira que el lo está cargando desde el puerto 5000 y algo).
Voy a detallar aquí porque pasa, por si alguien más tiene la misma duda:
Si te fijas en el error que publicaste, tu página HTML intenta acceder a una dirección en tu computadora.
Access to script at file://C:/users/Luis....my-element.js
Los navegadores no deberían poder acceder a archivos de tu computadora, ¿te imaginas que pasaría si yo abriera un sitio web que intentara cargar la siguiente ubicación en tu sistema?
Access to script at file://C:/users/Luis/.../contenidoprivado.txt
Entonces por defecto bloquearán cualquier url que no empiece con un protocolo web (http y https, principalmente).
Para solucionar el problema tienes que cargar tu archivo con un servidor web, para que en lugar de acceder a la url que empieza con "file:///" acceda por medio de http.
const template =document.createElement('div');template.innerHTML=`<style>.texto{color:red;}p{color:blue;}</style><pclass="texto">Hola mundo 2 ;)</p><p>Texto ejemplo para la clase</p>`;classmyElementextendsHTMLElement{constructor(){super();//* Obtenemos acceso a todos los elementos y métodos de la clase que extendemos (heredamos)//*Aquí solo estamos creando la etiqueta p, pero aún no la veremos en el DOMthis.p=document.createElement('p');//*Está lista y cargada en memoria}//*Esto es lo que agregará cosas al DOMconnectedCallback(){this.p.textContent="Hola mundo con vanilla JS";this.append(this.p);//* Aquí lo agregamos :Dthis.append(template);//*Agregamos al DOM el template que creamos al inicio y tendrá los estilos que definimos}}customElements.define('my-element', myElement);//* Definimos que la clase se va a convertir en una etiqueta
Me encanto esta clase. Me siento como un nene con su nuevo jueguete
por ahí me encontré con esto espero les ayude
Gracias, aunque la próxima vez deberías especificar qué es lo que estás compartiendo para saber precisamente qué es. En este caso parece ser un tutorial de cómo agregar componentes web a cualquier app.
Tremendo, en mi caso que no me quiero atar a ninguna tecnología, es perfecto! ♥
Hola, comunidad. Me surgió la misma duda que se aborda en este hilo de comentarios, pero decidí crear un hilo distinto para que se vea mejor visualmente.
.
Al seguir los pasos de la clase, se desarrolla el siguiente código:
.
.
Y se obtiene el siguiente resultado:
.
.
.
Mi duda en ese momento fue: si se está haciendo appendChild de template en cada instancia, ¿por qué sólo se ve en la última?
.
Reflexioné, testeé y llegué a esta conclusión: template es un objeto de JavaScript; por lo tanto, en memoria sólo existe la referencia a un elemento HTML. (Para ver recursos sobre referencia vs. valor en JavaScript ver el final de este comentario).
.
Lo que sucede es que se crea un nodo y su referencia se almacena en la variable template y, como sólo existe un nodo, al hacer appendChild en cada instancia, ese mismo nodo se le añade; pero se remueve cuando otra instancia le hace appendChild. Por eso sólo queda en el último <my-element>.
.
Si se quisiera que cada instancia de <my-element> tenga un nodo como template, éste debe agregarse al constructor, tal como fue el caso de this.p. De esta manera, se crea un this.template en cada instancia.
.
Resulta de la siguiente manera:
.
.
.
P. D.: Si estás de acuerdo en que más estudiantes vean esto, dale like para que este comentario llegue más arriba.
.
P. D. 2:Este comentario respecto a que los estilos definidos en el componente afectan lo que está afuera de él también me pareció muy interesante. Les recomiendo que le echen un ojo.
.
Recurso para aprender sobre referencia vs. valor en memoria en JavaScript: Video 1 & Video 2.
Justo pense lo mismo
Los nombres de las clases deben estar en PascalCase!
Hola:
Tengo una duda.
Porque si en el minuto 13:38, aparecen 3 etiquetas de Hola mundo!!, en el minuto 13:42, no aparecen 3 veces:
hola Mundo!!
Hola mundo 2!
texto ejemplo para la case!
hola Mundo!!
Hola mundo 2!
texto ejemplo para la case!
hola Mundo!!
Hola mundo 2!
texto ejemplo para la case!
Saludos :)
El profesor borró dos etiquetas <my-element>, al dejar una sola, como se ve en el siguiente código fuente, entonces solo va a mostrar un Hola Mundo !!!
De acuerdo a la documentación de appendChild. El hijo(template) hace referencia a un nodo que ya existe en el DOM (my-element), asi que lo quita el padre actual y lo pasa al ultimo.
<my-element></my-element>/* 1) Aqui se cargaria el template*/<my-element></my-element>/* 2) El appendChild ve que existe un nuevo nodo padre con el mismo nombre asi que pasa el template a este nuevo nodo */
La creación de mi primer Web Component me gustó mucho. Esta clase ha sido increíble.
Les comparto el codigo de la creacion de su propio web component! :D
Custom Elements
Vamos a crear nuestro primer Web Component :D
Creando el componente
En vs-code creamos un archivo HTML y JS :
Después de crear nuestra estructura básica de HTML vamos a nuestro archivo de JavaScript y crearemos una clase que será nuestro componente:
classMyElementextendsHTMLElement{constructor(){// Heredamos las propiedades de HTML element con "super()"super();}}customElements.define('my-element',MyElement);
Para crear un componente primero debemos crear una clase que extienda a lago llamado el HTMLElement, el cual nos va a permitir definir nuestro componente web.
Para convertir nuestro componente en una etiqueta HTML debemos usar el método estatico de customElements llamado define( ), el cual recibe dos parámetros, el primero el nombre de como se llamará la etiqueta de nuestro componente, y el segundo la clase que utilizaremos para definir los comportamientos de nuestro componente .
En este punto ya generamos nuestra etiqueta, y podemos usarla en el HTML:
Ahora vamos a empezar a definir los comportamientos de nuestro componente:
classMyElementextendsHTMLElement{constructor(){super();// Creamos una etiqueta pthis.p=document.createElement('p');}// Creamos el método de conenxiónconnectedCallback(){// Introducimos texto a nuestra etiquetathis.p.textContent='Hola mundo';// Agregamos esta etiqueta como hijo de <my-element> this.appendChild(this.p)}}customElements.define('my-element',MyElement);
Si vemos nuestro archivo en el navegador podremos ver ya nuestro “Hola mundo”.
Complejidad de componente
Podemos añadir muchas más cosas a nuestro componente, tales como estilos y demás, veamos un ejemplo.
// Creamos un divconst template =document.createElement('div');/* Dentro de este div podemos escribir estilos y demás cosas,
recordemos qu estos estilos no harán colición con otros estilos
debido a que los estilos que escribamos aquí solo aplican a nuestro
componente y NO a otros elementos externos a este
*/template.innerHTML=`<style> p {
color: #9C27B0
}
.text {
font-size: 1rem;
color: #212121;
}
</style><p>Hola mundo 2</p><pclass="text">Texto de ejemplo</p>`classMyElementextendsHTMLElement{constructor(){super();this.p=document.createElement('p');}connectedCallback(){this.p.textContent='Hola mundo';this.appendChild(this.p);// Añadimos nuestro template a nuestro componente como hijothis.appendChild(template)}}customElements.define('my-element',MyElement);
Y si todo esta correcto ,veremos nuestro componente en el navegador con todo lo que definimos:
Hola, por qué se debe agregar el archivo my-element.js como modulo?
Porque ES Modules es una de las 4 Web APIs necesarias para poder crear Web Components. Por lo que hemos visto en el curso, esta web API es la que se encarga de permitirnos encapsular el código de los componentes
porque si se agrego el template al componente y al escribir tres veces la etiqueta solo aparece el template en una?
De acuerdo a la documentación de appendChild. El hijo(template) hace referencia a un nodo que ya existe en el DOM (my-element), asi que lo quita el padre actual y lo pasa al ultimo. Seguramente más adelante en el curso tendrémos una solución para poder reusar componentes en el mismo html.
Hola buen dia, chicos les queria dar una recomendacion, ya que intente crear mi propio custom element y para crear un titulo use this.title y me generaba error, y busque por 3 horas la solicion y resulto que, el error era debido a que title es una propiedad y/o metodo de HTMLElement y cuando la usaba para mi custom element chicaban ambas y se generaba el error, entonces si les llega a salir un error similar miren que no esten usando el nombre de una palabra reservada, les dejo la linea de codigo para que revisen las propiedades y/o metodos del HTMLElement:
console.dir(HTMLElement.prototype);
En un video anterior se mencionaba que los Web Components tienen un encapsulado perfecto, pero al menos este ejemplo parece que no lo es, pues los estilos definidos en el componente afectan lo que esta afuera de él.
Supongo que es un tema que se tocará más adelante, xq incluso Diego menciona algo llamado "shadow DOM" que creo tenia que ver con eso, pero es bueno saber que no los Web Components no son inherentemente bien encapsulados y pueden afectar al resto de la pagina donde se usan.