> Manuales > Manual de LitElement

En los templates de LitElement podemos indicar muchas cosas de manera declarativa, como eventos y diversos tipos de bindings. Te hacemos un resumen con ejemplos de nuevos componentes.

Ahora que conocemos lo básico de los templates de LitElement queremos ser un poco más detallistas, enumerando los distintos tipos de declaraciones que puedes realizar en ellos. Hay algunos detalles interesantes que debemos dejar claros, para que puedas adquirir cierta soltura desarrollando tus componentes.

En este artículo además veremos diversos ejemplos con nuevos componentes, que esperamos añadan valor práctico a lo que llevamos explicado hasta el momento en el Manual de LitElement.

En resumen, estos son los tipos de declaraciones que puedes realizar en los templates.

Binding como contenido al texto:

<b>${this.prop}</b>

Binding a atributo:

<a href="${this.url}">Clic!</a>

Binding a atributo boleano:

<input type="checkbox" ?checked="${this.activo}">

Binding a propiedad:

<input type="text" .value="${this.cadena}">

Binding a manejador de evento:

<button @click="${this.hacerAlgo}">Clic!</button>

Ahora te vamos a mostrar cada una de estas declaraciones en distintos ejemplos de componentes sencillos.

Binding del contenido de una propiedad al texto

Esta es la manera más sencilla del enlace de datos, cuando queremos volcar una propiedad en el propio template, como un simple texto.

En el binding hacia el texto simplemente indicamos la propiedad que debe aparecer, con la sintaxis típica de los template strings: ${this.miPropiedad}.

En este primer ejemplo de componente hemos hecho algo realmente sencillo. El componente simplemente muestra un párrafo con un mensaje, que se define mediante el valor de una propiedad.

import { LitElement, html } from 'lit-element';

class MyMsg extends LitElement {
  static get properties() {
    return {
      text: { type: String }
    };
  }
  render() {
    return html`
      <p>${this.text}</p>
    `;
  }
}

customElements.define('my-msg', MyMsg);

En este componente, en su template, tenemos el volcado de la propiedad en el texto del template, con ${this.text}. No tiene mucho misterio.

Si quisieras usar ese componente, podrías enviar el mensaje asignando un texto para el mensaje por medio del atributo msg:

<my-msg text="Aprendo LitElement en DesarrolloWeb"></my-msg>
Nota: LitElement hace el trabajo de sincronizar el valor del atributo "text" en la etiqueta "my-msg" a la correspondiente propiedad "text" del custom element. Es decir, el atributo es seteado desde fuera y su valor se usa dentro del componente por medio de una propiedad. Más tarde en este artículo hablaremos de propiedades y atributos para aclarar posibles dudas.

Binding a atributo

Otra de las declaraciones más simples en los templates, que también hemos visto en ejemplos en este manual, es el binding a atributo. Básicamente sirve para colocar alguna cosa en uno de los atributos de una etiqueta nativa del HTML o custom element.

El binding a atributo se expresa de manera similar al caso anterior, pero ahora colocando la propiedad como valor de un atributo de la etiqueta. Usamos por supuesto la misma sintaxis típica para interpolación que nos viene dada por los template string de Javascript. Por ejemplo, id="${this.idElemento}".

En el siguiente ejemplo de componente, realizado para aclarar este punto, tenemos un binding a un atributo href de un enlace. Es un ejemplo muy sencillo que vamos a hacer para mostrar un enlace aleatorio. Según se inicializa el componente, en el constructor, se asigna aleatoriamente el valor una propiedad "url". Esa propiedad es la que usamos en el template para hacer el binding a atributo.

import { LitElement, html } from 'lit-element';

class RandomLink extends LitElement {
  static get properties() {
    return {
      url: { type: String }
    };
  }
  constructor() {
    super();
    this.url = (Math.round(Math.random())) ? 'https://desarrolloweb.com' : 'https://escuela.it';
  }
  render() {
    return html`
      <a href="${this.url}">Enlace aleatorio</a>
    `;
  }
}

customElements.define('random-link', RandomLink);

Ten en cuenta que los atributos del HTML contienen siempre valores con cadenas de caracteres, por lo el valor bindeado al atributo debería de ser siempre una cadena o algo que tenga conversión directa a una cadena.

Binding a atributo boleano

Hay veces en las que el atributo al que queremos bindear es un boleano. En HTML, es indiferente el valor que se asigne a este tipo de atributos. En realidad lo que importa es si están o no presentes en la etiqueta. Si está el atributo boleano, entonces se entiende que es verdadero. Si el atributo no aparece, entonces se entiende que es falso. Un claro ejemplo lo tenemos en el atributo "checked" de un input checkbox.

Este caso de binding tiene una sintaxis especial, que comienza por un interrogante: ?checked="${this.activo}".

El siguiente componente de ejemplo tiene un binding a atributo boleano. Al iniciarse el componente se asigna un valor a la propiedad activo, que luego se bindea en un campo checkbox.

import { LitElement, html } from 'lit-element';

class SiNo extends LitElement {
  static get properties() {
    return {
      activo: { type: Boolean }
    };
  }
  constructor() {
    super();
    this.activo = true;
  }
  render() {
    return html`
      <p>
        <input type="checkbox" ?checked="${this.activo}">
      </p>
    `;
  }
}

customElements.define('si-no', SiNo);

De momento este ejemplo de componente es un poco tonto. Para poder completarlo vamos a aprender cómo declarar los binding a eventos.

Binding a un manejador de eventos

El binding a manejador de eventos lo usamos cuando, de manera declarativa en el template, queremos asignar un método como manejador de un evento determinado.

Este tipo de binding se declara anteponiendo una "@" antes del nombre del evento que queremos usar. Podemos usar nombres de eventos nativos del HTML o bien nombres de eventos personalizados que los componentes disparen. Como valor le colocamos la referencia al método del componente que debe servir de manejador de eventos.

Por ejemplo, @click="${this.hacerAlgo}" asociaría el método del componente "hacerAlgo" como manejador de evento click.

En el siguiente código te mostramos el template del componente "si-no" anterior, modificado para agregar el ejemplo de binding a un manejador de evento.

render() {
  return html`
    <p>
      ${this.activo? 'Activo' : 'Inactivo'}
      <input type="checkbox" ?checked="${this.activo}" @change="${this.doChange}">
    </p>
  `;
}

Por supuesto, cualquier manejador de evento que declaremos en el template debe encontrarse dentro del componente. Por tanto, ahora tenemos que agregar este método "doChange" a nuestro componente de ejemplo.

doChange(e) {
  this.activo = e.target.checked;
}

Puedes observar que los manejadores de eventos reciben el objeto evento de Javascript. A partir de ese objeto soy capaz de llegar al elemento (e.target) y luego a la propiedad checked (e.target.checked) para saber, si después de este cambio, se ha quedado marcado o no el checkbox.

Nota: No te preocupes si hay partes que no te quedan especialmente claras, pues tenemos que volver más adelante sobre eventos y explicar muchas cosas importantes, en las que no queremos entrar ahora.

Ahora el componente completo tiene este código:

import { LitElement, html } from 'lit-element';

class SiNo extends LitElement {
  static get properties() {
    return {
      activo: { type: Boolean }
    };
  }
  constructor() {
    super();
    this.activo = true;
  }
  render() {
    return html`
      <p>
        ${this.activo? 'Activo' : 'Inactivo'}
        <input type="checkbox" ?checked="${this.activo}" @change="${this.doChange}">
      </p>
    `;
  }
  doChange(e) {
    this.activo = e.target.checked;
  }
}

customElements.define('si-no', SiNo);

Bind a propiedad

El binding a propiedad es el que permite enlazar un dato directamente como valor de una propiedad del componente, o elemento del DOM en caso de ser una etiqueta HTML nativa.

Esto se consigue con la notación del punto, poniendo un "." delante de la propiedad a la que estamos bindeando, seguido de su valor. Por ejemplo, para un input de tipo text, podemos hacer un binding sobre su propiedad "value".

<input type="text" .value="${this.unValor}">

Nota sobre Propiedades y atributos

Una duda típica es distinguir entre propiedad y atributo. Puede ser un poco confuso porque muchas veces los elementos tienen propiedades y atributos, que generalmente comparten el mismo nombre. Por ejemplo la propiedad "value" de un campo input text se setea desde el HTML con un atributo que se llama "value" en la etiqueta input. En general, cuando nos referimos a atributos estamos hablando de aquello que está escrito en la etiqueta o el componente. Por otra parte, dentro del componente u objeto del DOM existe la propiedad (propiedad del objeto del componente). En resumen, el valor de la propiedad dentro del componente muchas veces se ha seteado desde fuera, con el atributo.

LitElement, o el propio navegador en el caso de las etiquetas HTML, se encargan de asignar los valores que hayan en los atributos dentro de las propiedades del componente. Por eso puede parece que el binding por atributo puede funcionar de manera parecida al binding por propiedad.

La clave está en que, en los atributos, todo lo que podemos bindear deben ser cadenas de caracteres, o algo que equivalga directamente a una cadena, como un número. Si lo que deseamos bindear como atributo es algo más complejo, como un array, veremos que se recibe un error. En ese caso estamos obligados a usar el binding de propiedad.

Ejemplo de binding a propiedad

Este ejemplo consta de dos componentes. Uno de los componentes es un elemento lista, que simplemente tiene una propiedad que es un array y en su template recorre ese array para mostrar los ítems en una lista LI.

import { LitElement, html } from 'lit-element';

class ListElement extends LitElement {
  static get properties() {
    return {
      items: { type: Array }    
    };
  }
  render() {
    return html`
      <ul>
        ${this.items.map(item => html`<li>${item}</li>`)}
      </ul>
    `;
  }
}

customElements.define('list-element', ListElement);

Ahora tenemos otro componente que define un array con una lista de colores. La idea es mostrar ese array en una lista, para lo que usaremos el componente anterior "list-element"

import { LitElement, html } from 'lit-element';
import './list-element.js';

class ColorList extends LitElement {
  static get properties() {
    return {
      colors: { type: Array }
    };
  }
  constructor() {
    super();
    this.colors = ['Rojo', 'Verde', 'Amarillo', 'Azul'];
  }
  render() {
    return html`
      <list-element .items="${this.colors}"></list-element>
    `;
  }
}

customElements.define('color-list', ColorList);

En este último componente es donde podemos observar la línea del binding a la propiedad "items" del componente list-element.

<list-element .items="${this.colors}"></list-element>

El binding de propiedad en este ejemplo además de adecuado, porque realmente estamos bindeando a una propiedad, es también es obligatorio. Es decir, si intentas bindear al atributo, te dará un error.

Conclusión a la sintaxis en templates de LitElement

Hemos conocido las distintas declaraciones que podemos incorporar en un template en cuanto a binding de datos.

Ten en cuenta que todos estos enlaces son de una única dirección. Van desde el padre a los hijos. Sin embargo, aunque los hijos cambien el valor de sus propiedades, el cambio no viaja al padre. Es importante conocer y tener en cuenta este comportamiento de LitElement. No me alargo más, pues es un tema que pienso tratar en el artículo siguiente.

Miguel Angel Alvarez

Fundador de DesarrolloWeb.com y la plataforma de formación online EscuelaIT. Com...

Manual