Haciendo nuestro Web Component dinámico
En la clase anterior creamos un Web Component básico que mostraba un avatar fijo. Ahora vamos a mejorarlo para que sea dinámico, permitiendo configurar qué usuario y de qué servicio queremos mostrar el avatar.
El problema con nuestro componente actual
Nuestro componente actual siempre muestra el mismo avatar:
render() {
this.shadowRoot.innerHTML = `
<img src="https://avatars.githubusercontent.com/u/60507236?v=4" />
`
}
Esto no es muy útil. Lo ideal sería poder usarlo así:
<!-- Avatar de diferentes usuarios -->
<devjobs-avatar username="midudev"></devjobs-avatar>
<devjobs-avatar username="pepe"></devjobs-avatar>
<!-- Avatares de diferentes servicios -->
<devjobs-avatar service="twitter" username="midudev"></devjobs-avatar>
<devjobs-avatar service="youtube" username="midudev"></devjobs-avatar>
<!-- Con diferentes tamaños -->
<devjobs-avatar username="midudev" size="80"></devjobs-avatar>
Vamos a implementar esto paso a paso.
Leyendo atributos HTML con getAttribute
Los elementos HTML pueden tener atributos, y podemos leerlos con el método getAttribute():
render() {
const username = this.getAttribute('username')
console.log(username) // 'midudev' si usamos <devjobs-avatar username="midudev">
}
Si el atributo no existe, getAttribute() devuelve null. Podemos usar el operador de fusión nula (??) para establecer valores por defecto:
const username = this.getAttribute('username') ?? 'midudev'
const service = this.getAttribute('service') ?? 'github'
const size = this.getAttribute('size') ?? '40'
Ahora:
- Si usamos
<devjobs-avatar username="pepe">,usernameserá'pepe' - Si usamos
<devjobs-avatar>sin atributos,usernameserá'midudev'(el valor por defecto)
Creando un método auxiliar para las URLs
En lugar de construir la URL directamente en el método render(), vamos a crear un método auxiliar que se encargue de esto:
createUrl(service, username) {
return `https://unavatar.io/${service}/${username}`
}
Este método recibe el servicio y el usuario, y devuelve la URL completa:
createUrl('github', 'midudev')
// → 'https://unavatar.io/github/midudev'
createUrl('twitter', 'midudev')
// → 'https://unavatar.io/twitter/midudev'
createUrl('youtube', 'midudev')
// → 'https://unavatar.io/youtube/midudev'
💡 unavatar.io es un servicio gratuito que proporciona avatares de múltiples plataformas de forma unificada. Funciona con GitHub, Twitter, YouTube, Instagram, y muchas más.
Haciendo el render dinámico
Ahora juntamos todo en el método render():
render() {
// 1. Leemos los atributos con valores por defecto
const service = this.getAttribute('service') ?? 'github'
const username = this.getAttribute('username') ?? 'midudev'
const size = this.getAttribute('size') ?? '40'
// 2. Generamos la URL usando nuestro método auxiliar
const url = this.createUrl(service, username)
// 3. Renderizamos con los valores dinámicos
this.shadowRoot.innerHTML = `
<style>
img {
width: ${size}px;
height: ${size}px;
border-radius: 9999px;
}
</style>
<img
src="${url}"
alt="Avatar de ${username}"
class="avatar"
/>
`
}
Desglosando el código:
Paso 1: Lectura de atributos
const service = this.getAttribute('service') ?? 'github'
const username = this.getAttribute('username') ?? 'midudev'
const size = this.getAttribute('size') ?? '40'
- Leemos cada atributo del elemento HTML
- Si no existe, usamos un valor por defecto con el operador
?? - Ahora estos valores son variables que podemos usar en nuestro template
Paso 2: Generar la URL
const url = this.createUrl(service, username)
Llamamos a nuestro método auxiliar que construye la URL completa según el servicio.
Paso 3: Template con valores dinámicos
this.shadowRoot.innerHTML = `
<style>
img {
width: ${size}px;
height: ${size}px;
border-radius: 9999px;
}
</style>
<img
src="${url}"
alt="Avatar de ${username}"
class="avatar"
/>
`
Usamos template strings para insertar los valores dinámicos:
${size}px: El ancho y alto vienen del atributosize${url}: La URL generada por nuestro método${username}: El nombre de usuario para el texto alternativoborder-radius: 9999px: Hace la imagen completamente circular
El código completo
class DevJobsAvatar extends HTMLElement {
constructor() {
super() // llamar al constructor de HTMLElement
this.attachShadow({ mode: 'open' })
}
createUrl(service, username) {
return `https://unavatar.io/${service}/${username}`
}
render() {
const service = this.getAttribute('service') ?? 'github'
const username = this.getAttribute('username') ?? 'midudev'
const size = this.getAttribute('size') ?? '40'
const url = this.createUrl(service, username)
this.shadowRoot.innerHTML = `
<style>
img {
width: ${size}px;
height: ${size}px;
border-radius: 9999px;
}
</style>
<img
src="${url}"
alt="Avatar de ${username}"
class="avatar"
/>
`
}
connectedCallback() {
this.render()
}
}
customElements.define('devjobs-avatar', DevJobsAvatar)
Usando nuestro componente mejorado
Ahora podemos usar el componente de múltiples formas:
<!-- Avatar por defecto (GitHub, midudev, 40px) -->
<devjobs-avatar></devjobs-avatar>
<!-- Personalizar el usuario -->
<devjobs-avatar username="pepe"></devjobs-avatar>
<!-- Personalizar el tamaño -->
<devjobs-avatar username="midudev" size="80"></devjobs-avatar>
<!-- Avatar de Twitter -->
<devjobs-avatar service="twitter" username="midudev"></devjobs-avatar>
<!-- Avatar de YouTube con tamaño custom -->
<devjobs-avatar service="youtube" username="midudev" size="100"> </devjobs-avatar>
<!-- Múltiples avatares en la misma página -->
<devjobs-avatar username="midudev" size="50"></devjobs-avatar>
<devjobs-avatar username="pepe" size="50"></devjobs-avatar>
<devjobs-avatar username="juan" size="50"></devjobs-avatar>
Cada instancia del componente es independiente y puede tener sus propios valores.
Ventajas de este enfoque
Reutilización
El mismo componente se puede usar de muchas formas diferentes simplemente cambiando los atributos HTML.
Encapsulación
Gracias al Shadow DOM:
- Los estilos del componente no afectan al resto de la página
- Los estilos globales no afectan al componente
- Cada instancia tiene su propio árbol DOM aislado
Simplicidad de uso
Una vez definido el componente, usarlo es tan simple como escribir HTML:
<devjobs-avatar username="midudev"></devjobs-avatar>
No necesitas llamar funciones, pasar parámetros complejos ni configurar nada. Todo funciona de forma declarativa.
Sin dependencias
Todo esto funciona con JavaScript puro, sin necesidad de React, Vue, Angular ni ningún framework.
Creando instancias desde JavaScript
También podemos crear y configurar componentes desde JavaScript:
// Crear el elemento
const avatar = document.createElement('devjobs-avatar')
// Configurar atributos
avatar.setAttribute('username', 'pepe')
avatar.setAttribute('size', '60')
avatar.setAttribute('service', 'github')
// Añadir al DOM
document.body.appendChild(avatar)
O de forma más directa:
const avatar = document.createElement('devjobs-avatar')
avatar.username = 'pepe' // También podemos asignar propiedades
document.querySelector('#avatars').appendChild(avatar)
¡Lo que hemos aprendido!
- Usamos
getAttribute()para leer atributos HTML del componente - El operador
??nos permite establecer valores por defecto cuando los atributos no existen - Podemos crear métodos auxiliares como
createUrl()para organizar mejor el código - Los template strings permiten insertar valores dinámicos en el HTML y CSS
- El Shadow DOM garantiza que los estilos estén encapsulados
- Un mismo componente puede usarse múltiples veces con diferentes configuraciones
- Los Web Components funcionan de forma declarativa, como cualquier elemento HTML
Con estas técnicas puedes crear componentes reutilizables y configurables que funcionen en cualquier proyecto web, sin depender de frameworks externos.