En la lección anterior interpolaste tu primer valor dentro de una plantilla html, sustituyendo un texto fijo por un campo de instancia. Pero una interpolación dentro de ${} puede hacer mucho más que insertar un texto sencillo: puede rellenar un atributo HTML, activar o desactivar un atributo booleano, asignar directamente una propiedad de un elemento del DOM, o evaluar cualquier expresión JavaScript arbitraria. Esta lección recorre en detalle los distintos tipos de interpolación que admite Lit, con ejemplos concretos sobre <task-card>, y sienta las bases sintácticas que usarás durante el resto del curso.
Contenido
- Los cuatro destinos de una interpolación
- Interpolación de texto en el contenido de un nodo
- Interpolación de atributos HTML normales
- Interpolación de propiedades del DOM con el prefijo
. - Atributos booleanos con el prefijo
? - Los eventos con
@: una mención de paso - Expresiones JavaScript arbitrarias dentro de
${} - Anidar plantillas
htmldentro de otras - Ampliando
<task-card>con varios campos
- Los cuatro destinos de una interpolación
Cuando escribes ${...} dentro de una plantilla html, Lit necesita decidir cómo aplicar ese valor al DOM, y la decisión depende de dónde, sintácticamente, se ha colocado la interpolación. Existen cuatro destinos principales, cada uno con su propia sintaxis:
| Destino | Sintaxis en la plantilla | Para qué sirve |
|---|---|---|
| Contenido de un nodo | <p>${valor}</p> |
Insertar texto (o incluso otra plantilla) como contenido de un elemento |
| Atributo HTML | <div id="${valor}"> |
Establecer el valor de un atributo del elemento, como cualquier atributo HTML |
| Propiedad del DOM | <input .value="${valor}"> |
Asignar directamente una propiedad JavaScript del elemento, sin pasar por un atributo |
| Atributo booleano | <button ?disabled="${valor}"> |
Añadir o quitar un atributo por completo según si el valor es "verdadero" o "falso" |
A estos cuatro se suma un quinto destino, los manejadores de eventos con el prefijo @ (<button @click="${...}">), que se presenta brevemente en el apartado 6 de esta lección, pero cuyo estudio en profundidad corresponde al módulo 5, "Eventos y Comunicación entre Componentes". Los apartados siguientes desarrollan cada uno de los cuatro destinos principales.
- Interpolación de texto en el contenido de un nodo
Es el tipo de interpolación que ya usaste en la lección anterior: colocar ${...} directamente entre las etiquetas de apertura y cierre de un elemento.
Lit inserta el valor de this.titulo como contenido del <h3>, tratándolo siempre como texto plano, nunca como HTML. Esto tiene una consecuencia importante para la seguridad: si this.titulo contuviera, por ejemplo, la cadena '<script>alert(1)</script>', Lit lo mostraría literalmente como texto visible en pantalla (las etiquetas de apertura y cierre incluidas, como caracteres), en lugar de ejecutarlo como código. Esta es una diferencia deliberada respecto a innerHTML, ya comentada en la lección anterior, y es el comportamiento por defecto y recomendado en la inmensa mayoría de los casos.
Este destino también admite valores que no son cadenas de texto: números, true/false (mostrados literalmente como texto "true" o "false"), null y undefined (que no muestran nada), arrays de valores (se verá en la lección de renderizado de listas), e incluso el resultado de otra plantilla html anidada, como se explica en el apartado 8.
- Interpolación de atributos HTML normales
Cuando la interpolación aparece dentro de las comillas del valor de un atributo estándar de HTML, Lit la trata como una asignación de atributo:
Este destino equivale, aproximadamente, a llamar a elemento.setAttribute('id', valor). Es el destino adecuado para atributos que existen en el HTML como texto: id, class, title, href, src, atributos data-* personalizados, atributos ARIA de accesibilidad, etc. Un detalle a tener en cuenta: si el valor interpolado es undefined, Lit elimina el atributo por completo en lugar de establecerlo con el texto "undefined"; este comportamiento resulta útil para atributos opcionales, aunque el módulo 7 presenta una directiva específica (ifDefined) pensada justamente para expresar esta intención de forma más explícita.
- Interpolación de propiedades del DOM con el prefijo
.
.A veces no interesa establecer un atributo HTML (que siempre es texto), sino asignar directamente una propiedad de JavaScript del elemento del DOM, que puede contener cualquier tipo de valor: un objeto, un array, un booleano nativo, una función. Para esto, Lit ofrece la sintaxis con un punto delante del nombre:
Aquí, .value="${this.textoBusqueda}" no crea ni modifica ningún atributo value en el HTML; en su lugar, ejecuta directamente elemento.value = this.textoBusqueda, asignando la propiedad JavaScript del elemento <input>. La diferencia entre atributo y propiedad puede parecer sutil, pero es importante: muchos elementos nativos del navegador (como <input>) mantienen su atributo HTML original (el que se escribió en el marcado) separado de su propiedad JavaScript actual (el valor que el usuario ha escrito en el campo), y en muchos casos interesa trabajar con la propiedad, no con el atributo.
Como referencia rápida para decidir cuál usar:
| Situación | Destino recomendado |
|---|---|
| El valor es siempre una cadena de texto simple y tiene sentido que aparezca en el HTML | Atributo (attr="${valor}") |
| El valor es un objeto, un array, o cualquier tipo de dato que no sea texto | Propiedad (.prop="${valor}") |
Se trabaja con una propiedad específica del DOM que no tiene atributo HTML equivalente exacto (como .value en formularios avanzados) |
Propiedad (.prop="${valor}") |
Este mismo mecanismo, el de pasar propiedades (no solo atributos) a un elemento, es también la base de cómo un componente Lit padre pasará datos complejos (objetos, arrays) a un componente Lit hijo; se retomará en detalle en el módulo 5 al hablar de comunicación entre componentes.
- Atributos booleanos con el prefijo
?
?Algunos atributos de HTML son "booleanos" en un sentido especial: su sola presencia en la etiqueta activa el comportamiento, independientemente del texto que se les asigne. disabled, checked, hidden o required son ejemplos habituales. Escribir disabled="false" en HTML puro, de hecho, no desactiva el botón: el atributo sigue estando presente, así que el navegador lo interpreta igualmente como deshabilitado.
Para gestionar correctamente este tipo de atributos, Lit ofrece la sintaxis con el prefijo ?:
Con ?disabled="${this.estaBloqueado}", Lit añade el atributo disabled al elemento solo si this.estaBloqueado es un valor "verdadero" (true, o cualquier valor que JavaScript trate como verdadero), y lo elimina por completo del elemento si es "falso" (false, null, undefined, 0, cadena vacía...). Esto reproduce exactamente la semántica real de los atributos booleanos de HTML: lo que importa no es el texto del atributo, sino si está presente o no.
- Los eventos con
@: una mención de paso
@: una mención de pasoExiste un quinto tipo de interpolación, con el prefijo @, pensado para asociar manejadores de eventos del DOM a un elemento:
Esta sintaxis equivale, de forma simplificada, a elemento.addEventListener('click', this.manejarClic). Se menciona aquí únicamente para que reconozcas la sintaxis si la ves en ejemplos o en la documentación oficial de Lit durante este módulo; su estudio completo —incluyendo cómo definir correctamente el método manejador, el problema del valor de this dentro de él, y cómo despachar eventos personalizados entre componentes— es el contenido íntegro del módulo 5, "Eventos y Comunicación entre Componentes". Por ahora, ninguna de las plantillas de este módulo usará todavía manejadores de eventos.
- Expresiones JavaScript arbitrarias dentro de
${}
${}Un aspecto que conviene interiorizar cuanto antes: dentro de ${} no solo se pueden colocar nombres de variables o propiedades sueltas (${this.titulo}); se puede colocar cualquier expresión válida de JavaScript que produzca un valor. Esto incluye operaciones aritméticas, concatenación de texto, llamadas a métodos, operadores ternarios, y cualquier combinación de ellos:
render() {
return html`
<article>
<h3>${this.titulo.toUpperCase()}</h3>
<p>Prioridad: ${this.prioridad + 1} de 5</p>
<p>${this.completada ? 'Tarea finalizada' : 'Tarea pendiente'}</p>
</article>
`;
}En este ejemplo, ${this.titulo.toUpperCase()} llama al método toUpperCase() de la cadena antes de interpolar el resultado; ${this.prioridad + 1} realiza una operación aritmética; y ${this.completada ? 'Tarea finalizada' : 'Tarea pendiente'} usa un operador ternario para elegir entre dos textos según una condición. Este último patrón, el del operador ternario dentro de una interpolación, es tan habitual en Lit que la siguiente lección, "Renderizado Condicional", lo desarrolla en profundidad como técnica principal para mostrar u ocultar contenido.
Lo único que no se puede hacer dentro de ${} es escribir sentencias completas de JavaScript (un if con llaves, un bucle for, una declaración const): solo se admiten expresiones, es decir, código que se reduce a un único valor. Cuando se necesita lógica más elaborada que una simple expresión, la técnica habitual es extraerla a un método o función auxiliar de la clase, y llamar a ese método desde dentro de ${}, como se hace con ${this.titulo.toUpperCase()} en el ejemplo anterior.
- Anidar plantillas
html dentro de otras
html dentro de otrasComo se apuntó en el apartado 2, el resultado de una llamada a html (un objeto TemplateResult) es un valor perfectamente válido para interpolar dentro de otra plantilla html. Esto permite dividir una plantilla grande en fragmentos más pequeños y combinarlos:
render() {
const cabecera = html`<h3>${this.titulo}</h3>`;
return html`
<article>
${cabecera}
<p>Estado: ${this.estado}</p>
</article>
`;
}Aquí, cabecera es en sí misma el resultado de una llamada a html, y se interpola dentro de la plantilla principal exactamente igual que se interpolaría cualquier otro valor. Lit reconoce que se trata de otra plantilla (no de texto plano) y la inserta como HTML real, no como texto escapado. Esta capacidad de anidar plantillas es la base de dos técnicas que se estudiarán en las próximas lecciones: extraer fragmentos condicionales a funciones auxiliares (lección de renderizado condicional) y generar una plantilla por cada elemento de un array (lección de renderizado de listas).
- Ampliando
<task-card> con varios campos
<task-card> con varios camposCon todos los tipos de interpolación ya vistos, es el momento de ampliar <task-card> para mostrar varios campos de la tarea a la vez: título, estado y prioridad. Recuerda, como se explicó en la lección anterior, que estos siguen siendo campos de instancia simples, no propiedades reactivas todavía.
import { LitElement, html } from 'lit';
class TaskCard extends LitElement {
constructor() {
super();
this.titulo = 'Preparar la demo del sprint';
this.estado = 'En curso';
this.prioridad = 2;
this.urgente = false;
}
render() {
return html`
<article ?data-urgente="${this.urgente}">
<h3>${this.titulo}</h3>
<p>Estado: ${this.estado}</p>
<p>Prioridad: ${this.prioridad} de 5</p>
<p>${this.urgente ? '⚠ Requiere atención inmediata' : 'Sin urgencia especial'}</p>
</article>
`;
}
}
customElements.define('task-card', TaskCard);Repasemos las novedades:
${this.titulo},${this.estado}y${this.prioridad} de 5son interpolaciones de texto sencillas, como ya se vio en la lección anterior; la de la prioridad combina el valor interpolado con texto literal adicional dentro del mismo nodo.?data-urgente="${this.urgente}"usa la sintaxis de atributo booleano vista en el apartado 5: el atributodata-urgentesolo aparecerá en el<article>cuandothis.urgenteseatrue. Esto es útil, por ejemplo, para que unos estilos CSS (que se verán en el módulo 4) puedan aplicar un aspecto distinto a las tarjetas urgentes basándose en la presencia de ese atributo.- La última línea usa un operador ternario, como se explicó en el apartado 7, para mostrar un texto distinto según el valor de
this.urgente.
Al recargar la página, la tarjeta mostrará ahora varios datos combinados. Igual que en la lección anterior, si modificas estos campos desde la consola del navegador, la pantalla no se actualizará automáticamente: esa pieza sigue perteneciendo al módulo 3.
Errores Comunes y Consejos
- Usar
.propcuando bastaría un atributo normal: si el valor es siempre texto simple, no hay necesidad de usar la sintaxis con punto; el atributo normal (attr="${valor}") es más legible y suficiente en la mayoría de los casos. - Usar un atributo normal para un booleano: escribir
disabled="${this.estaBloqueado}"(sin el?) no produce el resultado esperado, porque Lit convertiría el valor a texto y lo asignaría como atributo con ese texto, en lugar de añadir o quitar el atributo según su condición. Para atributos booleanos reales, siempre?attr. - Intentar meter una sentencia completa dentro de
${}: código como${if (this.urgente) { return 'Sí'; }}no es válido, porque${}solo admite expresiones, no sentencias. La alternativa correcta es un operador ternario o extraer la lógica a un método aparte. - Olvidar que el contenido de texto se escapa automáticamente: si necesitas mostrar contenido HTML real generado dinámicamente (no simple texto), interpolar directamente no basta; existe un mecanismo específico para ese caso concreto (la directiva
unsafeHTML) que se estudiará, con sus riesgos de seguridad, en módulos posteriores. Por defecto, y en el 99% de los casos, la interpolación de texto es la opción correcta y más segura.
Ejercicios
- Añade a
<task-card>un nuevo campo de instanciathis.etiqueta(por ejemplo,'Backend') e interpólalo como el valor del atributodata-etiquetadel<article>. - Añade un campo
this.progreso(un número de 0 a 100) y muéstralo interpolado dentro de un párrafo junto con el símbolo de porcentaje, por ejemplo: "Progreso: 40%". - Modifica la interpolación booleana de
this.urgentedel apartado 9 para que, en lugar de un atributodata-urgente, controle el atributo booleano realhiddende un pequeño<span>con el texto "URGENTE" (es decir, que el<span>solo aparezca cuando la tarea sea urgente).
Soluciones
render() {
return html`
<article>
...
<span ?hidden="${!this.urgente}">URGENTE</span>
</article>
`;
}Nótese el uso de la negación (!this.urgente): como hidden oculta el elemento cuando está presente, hay que añadir el atributo precisamente cuando la tarea no es urgente, de ahí la negación del valor original.
Conclusión
En esta lección has recorrido los distintos destinos que puede tener una interpolación dentro de una plantilla html: contenido de texto, atributos HTML normales, propiedades del DOM con el prefijo ., y atributos booleanos con el prefijo ?, además de una mención de paso a los manejadores de eventos con @ que se detallarán en el módulo 5. Has visto también que dentro de ${} cabe cualquier expresión JavaScript, no solo nombres de variables sueltos, y que las plantillas html se pueden anidar unas dentro de otras. Con todo ello, <task-card> ahora combina varios campos interpolados: título, estado, prioridad y un indicador de urgencia.
En la siguiente lección, "Renderizado Condicional", te centrarás en una de las expresiones que ya has empezado a usar de pasada —el operador ternario dentro de ${}— y la desarrollarás en profundidad como técnica principal para mostrar contenido distinto según condiciones, aplicándola a una insignia de estado en <task-card> que cambiará según la tarea esté pendiente, en progreso o hecha.
Curso de Lit
Módulo 1: Introducción a Lit y Web Components
- ¿Qué son los Web Components y por qué Lit?
- Configuración del Entorno de Desarrollo
- Tu Primer Componente Lit
- Anatomía de un Componente Lit
Módulo 2: Plantillas Reactivas y Renderizado
- El Motor de Plantillas de Lit
- Expresiones e Interpolación en Plantillas
- Renderizado Condicional
- Renderizado de Listas
- El Ciclo de Renderizado
Módulo 3: Propiedades y Estado Reactivo
- Propiedades Reactivas
- Estado Interno con @state
- Tipos de Propiedades y Conversores Personalizados
- Atributos vs Propiedades y Reflexión
Módulo 4: Estilos en Componentes Lit
- CSS Encapsulado con Shadow DOM
- Estilos Compartidos entre Componentes
- Variables CSS Personalizadas y Theming
- Slots y Estilizado de Contenido Distribuido
Módulo 5: Eventos y Comunicación entre Componentes
- Manejo de Eventos DOM en Plantillas
- Eventos Personalizados: Comunicación Hijo a Padre
- Comunicación de Padre a Hijo con Propiedades
- Patrones de Comunicación entre Componentes Hermanos
Módulo 6: Ciclo de Vida y Comportamiento Avanzado
- Callbacks del Ciclo de Vida
- Hooks Reactivos: willUpdate, updated y firstUpdated
- Controladores Reactivos
- Mixins y Composición de Comportamiento
Módulo 7: Directivas y Funcionalidades Avanzadas de Plantillas
- Directivas Incorporadas: classMap, styleMap e ifDefined
- Directivas Personalizadas
- Renderizado Asíncrono con until
- Contexto Compartido con @lit/context
Módulo 8: Integración, Interoperabilidad y Despliegue
- Usar Componentes Lit en HTML Plano
- Integrar Lit con React, Vue y Angular
- Renderizado en el Servidor con @lit-labs/ssr
- Empaquetado, Publicación y TypeScript
Módulo 9: Pruebas y Buenas Prácticas
- Pruebas Unitarias con Web Test Runner
- Accesibilidad en Web Components
- Rendimiento y Optimización
- Patrones y Anti-patrones Comunes
