Condicionales y bucles en templates LitElement

  • Por
Aprende a crear estructuras condicionales y bucles o repeticiones en los templates de web components creados con LitElement.

En LitElement podemos definir templates que tienen algunas estructuras de control básicas como condicionales y repeticiones, lo que resultará muy útil para alterar el aspecto del componente en función del estado de sus propiedades.

En el presente artículo abordaremos estas herramientas disponibles en los templates desde un enfoque práctico. Vamos a construir un par de componentes. Uno para mostrar una ficha de un contacto con el que practicaremos las estructuras condicionales, y otro para mostrar un listado de contactos que tendremos en un array, donde practicaremos con las repeticiones.

Por supuesto, partimos de la base de conocimiento que ya hemos adquirido en lo que llevamos del Manual de LitElement.

Estructuras condicionales

Dado que el método que construye un template es puramente Javascript, LitElement hace uso de las herramientas del propio lenguaje para construir los condicionales. Para los condicionales LitElement saca partido del operador ternario y la posibilidad de embeber expresiones dentro de un template string.

Observa este código:

render() {
  return html`
    <div>
      ${this.activo? 'Estoy Activo!' : 'Estoy Inactivo'}
    </div>
  `;
}

En el template anterior estamos suponiendo que tienes una propiedad en el componente que se llama "activo". Dentro del template tenemos una expresión en la que no volcamos directamente el contenido de la variable, como en ejemplos anteriores del manual, sino que se evalúa de manera condicional, para mostrar una u otra cadena de texto.

No obstante, es fácil que nuestro ejemplo no sea tan sencillo y que nuestro condicional no implique mostrar una simple cadena, sino más cosas, como etiquetas, otros componentes, bindeo de otras propiedades, etc. Por ello, en la mayoría de los casos podremos usar una construcción más parecida a esta:

render() {
  return html`
    <div>
      ${this.activo 
        ? html`<p>Estoy activo a las ${this.hora}</p>` 
        : html`<p>Estoy inactivo desde las ${this.horaDescanso}</p>`
      }
    </div>
  `;
}

Aquí estamos apoyándonos de nuevo en la función "html", para crear dos sub-templates que tienen todo aquello que se quiera mostrar en el caso positivo y en el caso negativo del operador ternario. Fíjate también en esta propuesta de indentación que permite clarificar correctamente las dos partes del condicional.

Nota: Como ves, LitElement se basa mucho en lo que ya te ofrece Javascript como lenguaje, obteniendo gracias a ello un menor peso y alto rendimiento. Sin embargo, para los que vienen de Polymer podrá ser un poco chocante esta nueva manera de hacer las cosas, dado que antes se declaraba todo con código HTML, templates condicionales, de repetición, etc. Ahora se hacen las cosas más con programación, aunque el estilo tiene un aire a programación funcional, que se acentuará un poco más cuando veamos las repeticiones.

Ejemplo de componente con un condicional en el template

Vamos a realizar el primer componente de nuestro ejemplo. Se trata de una simple ficha de contacto. En ella se muestran dos datos de un supuesto contacto, el nombre y el email. Pero inicialmente el email permanece oculto y hay que pulsar sobre un enlace para hacerlo visible.

Voy a comenzar mostrando el código completo del componente "my-contact", para luego explicar sus distintas partes.

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

export class MyContact extends LitElement {
  static get properties() {
    return {
      nombre: {
        type: String
      },
      email: {
        type: String
      },
      verMas: {
        type: Boolean
      }
    };
  }

  constructor() {
    super();
    this.verMas = false;
  }

  render() {
    return html`
      <style>
      div {
        border: 1px solid #ddd;
        padding: 10px;
        border-radius: 5px;
        display: inline-block;
      }
      h1 {
        font-size: 1.2em;
        font-weight: normal;
      }
      </style>
      <div>
        <h1>${this.nombre}</h1>
        <p><a href="#" @click="${this.toggle}">Ver más</a></p>
        
        ${this.verMas?
          html`Email: ${this.email}` :
          ''  
        }

      </div>
    `;
  }

  toggle(e) {
    e.preventDefault();
    this.verMas = !this.verMas;
  }
}

customElements.define('my-contact', MyContact);

Ahora vamos a tocar unos puntos interesantes sobre el código del anterior componente.

Declaración de las propiedades

En este componente estamos declarando 3 propiedades. Aunque todavía no hemos hablado mucho de ellas, puedes entender por el código que dos son de tipo String y una tercera es de tipo Boolean.

Es importante decir que, para que LitElement sea capaz de ser reactivo en su template a los cambios en las propiedades, éstas deben declararse de manera obligatoria.

static get properties() {
  return {
    nombre: {
      type: String
    },
    email: {
      type: String
    },
    verMas: {
      type: Boolean
    }
  };
}

El constructor

La inicialización de las propiedades de los componentes LitElement se realiza dentro del constructor de la clase. En este ejemplo estamos inicializando tan solo una de sus propiedades, verMas, que queremos setear a "false" cuando se genere un nuevo elemento con este componente.

constructor() {
  super();
  this.verMas = false;
}

De todos modos, es importante mencionar que todas las propiedades pueden inicializarse vía los atributos de la etiqueta que escribimos para usar el componente. Por ejemplo, si vamos a inicializar el nombre y email del contacto con la propia etiqueta, usamos los atributos con algo parecido a esto:

<my-contact email="miguel@desarrolloweb.com" nombre="Miguel Angel Alvarez"></my-contact>

Las inicializaciones que pongas al usar el componente tienen preferencia sobre las que se escriben en los constructores. Por eso, si tenemos la etiqueta de este modo:

<my-contact vermas email="miguel@desarrolloweb.com" nombre="Miguel Angel Alvarez"></my-contact>

Gracias a que la etiqueta anterior tiene el atributo "vermas", estaremos inicializando la propiedad "verMas" a true, independientemente de lo que marque el constructor.

Nota: Como has visto, las propiedades de tipos booleanos se inicializan analizando si está presente o no el atributo en la etiqueta. Si el atributo "vermas" figura en la etiqueta de uso del componente, entonces se asigna un valor booleano verdadero a la propiedad. Si el atributo no está presente, simplemente se quedaría con el valor "undefined", que equivaldría a falso, aunque no es false exactamente. Por eso nos hemos tomado la molestia de inicializarlo en el constructor, aunque si no estuviera esa inicialización hay que decir que también funcionaría.

Método render

Este es el método más complejo del presente componente. En él hemos incluido unas pocas reglas de estilo. y luego el contenido HTML.

En el contenido HTML hemos colocado el nombre, accediendo a this.nombre.

Luego tenemos un enlace con un manejador de evento click. Al pulsarse el enlace se invocará al método this.toggle().

Por último tenemos la parte condicional. Si la propiedad "verMas" vale true, entonces se mostrará la parte del caso positivo, que es un sub-template, que muestra el email del usuario.

Método toggle

Este método lo hemos asociado como manejador de evento del clic sobre el enlace. Simplemente cambia el estado de la propiedad verMas, negándola, por lo que el template se actualizará, mostrando u ocultando la información del email

Estructura de repetición

Realizar un bucle para recorrer todos los elementos de un array y producir un template de repetición es bastante sencillo en LitElement. Para ello, la librería recurre de nuevo a funcionalidades disponibles en el lenguaje Javascript.

Tirando de la posibilidad de realizar cualquier tipo de expresión en un template string, se realiza el bucle con el método map() del array sobre el que queremos iterar.

render() {
  return html`
    <ul>
      ${this.frutas.map(fruta => html`<li>${fruta}</li>`)}
    </ul>
  `;
}

Este template genera una lista desordenada (ul) donde tenemos un item (li) por cada uno de los elementos del array.

El método map() de los arrays, nativo de Javascript, devuelve un nuevo array cuyos valores son devueltos por una función que se ejecuta para cada uno de los item del array. La función que se entrega a map() en este caso devuelve un sub-template, generado con la función "html". Así al ejecutarse map() lo que obtendremos es un array de templates que se renderizarán como contenido de la lista.

Nota: de nuevo estamos ante un código que tiene bastante sabor a programación funcional, que resulta además extremadamente compacto. Para los que venimos de Polymer donde se usaba HTML plano con los templates dom-repeat representa un cambio significativo. El código es quizás menos claro, al menos al principio, pero en breve te acostumbrarás. El rendimiento es mucho mayor, combinado con un peso de la librería menor, así que el cambio merece la pena.

Componente con template de repetición

Ahora nuestro segundo componente tiene que mostrar un listado de contactos. Para ello se apoyará en el componente que nos permitía mostrar un contacto indivicual, repitiéndolo por cada uno de los elementos de un array.

Veamos el código completo para comenzar.

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

export class MyContactList extends LitElement {
  static get properties() {
    return {
      contactos: {
        type: Array
      }
    };
  }

  constructor() {
    super();  
    this.contactos = [
      {
        nombre: 'Miguel Angel Alvarez',
        email: 'miguel@desarrolloweb.com'
      },
      {
        nombre: 'Alvaro Martínez',
        email: 'contacto@escuela.it'
      },
      {
        nombre: 'Jhon Doe',
        email: 'jhon@example.com'
      },
    ]
  }
  render() {
    return html`
      <div>
        ${this.contactos.map(contacto => html`<my-contact nombre="${contacto.nombre}" email="${contacto.email}"></my-contact>`)}
      </div>
    `;
  }

}

customElements.define('my-contact-list', MyContactList);

Visto el código anterior, vamos a repasar los puntos clave que debes apreciar para entenderlo completamente.

Imports

Puedes notar que ahora tenemos dos imports, ya que este componente se apoya en LitElement y también necesita conocer al componente del ejemplo anterior, my-contact, que es el que va a mostrar un contacto único.

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

Es importante darse cuenta aquí que los import a librerías instaladas vía npm se realizan a través del nombre del package, mientras que los import de código nuestro se realizan a través de la ruta. En este caso, dado que la ruta del segundo import es './my-contact.js', querrá decir que el archivo del componente actual, my-contact-list.js, está en la misma carpeta que el archivo del componente anterior, my-contact.js.

Declaración de propiedades

En este caso tenemos una única propiedad, que es el array de contactos que debemos iterar en el template. Indicamos que el tipo es un array, pero lo inicializamos en el constructor.

static get properties() {
  return {
    contactos: {
      type: Array
    }
  };
}

Constructor

Por sencillez vamos a inicializar en el constructor el array de contactos. Simplemente asignamos a la propiedad un literal de array con tres contactos.

No habíamos mencionado todavía un detalle importante de los constructores. Se trata de invocar siempre al constructor de la clase padre, con super(). Si no lo hacemos empezarán a fallar cosas! pues los mecanismos de inicialización de LitElement no se llegarían a ejecutar.

constructor() {
  super();  
  this.contactos = [
    {
      nombre: 'Miguel Angel Alvarez',
      email: 'miguel@desarrolloweb.com'
    },
    {
      nombre: 'Alvaro Martínez',
      email: 'contacto@escuela.it'
    },
    {
      nombre: 'Jhon Doe',
      email: 'jhon@example.com'
    },
  ]
}

Método render()

Este método tiene la parte más interesante, dado el punto en el que nos encontramos. Contiene una repetición, iterando por el array de contactos. Para cada contacto devolvemos un sub-template usando el componente "my-contact", que inicializamos con los valores del contacto actual.

Si ordenamos un poco el código creo que se puede leer bastante bien:

render() {
  return html`
    <div>
      ${this.contactos.map(contacto => html`
        <my-contact 
          nombre="${contacto.nombre}" 
          email="${contacto.email}"
        ></my-contact>
      `)}
    </div>
  `;
}

Conclusión a las estructuras en los templates

Como has podido comprobar, los templates programados en LitElement son concisos y utilizan la mayor cantidad posible de características del Javascript nativo de los navegadores. Es inevitable que no se venga a la cabeza una de las frases más repetidas del equipo de Polymer "Use the platform", haciendo alusión a la necesidad de usar lo máximo de todo aquello que el navegador ya ofrece, en lugar de escribir código propietario para resolver esas tareas.

Nos quedan cosas por aprender sobre los templates, y por supuesto sobre otros asuntos de LitElement, pero ya empezamos a saborear el estilo de programación con Web Components que propone.

En el siguiente artículo abordaremos otras de las declaraciones más útiles sobre los templates de LitElement.

Autor

Miguel Angel Alvarez

Miguel es fundador de DesarrolloWeb.com y la plataforma de formación online EscuelaIT. Comenzó en el mundo del desarrollo web en el año 1997, transformando su hobby en su trabajo.

Compartir