Más sobre binding en Polymer 2

  • Por
Diversas prácticas habituales en el binding de Polymer 2, operador de negación, computed bindings y compound bindings (bindeos computados y compuestos).

En este artículo del Manual de Polymer 2 vamos a tratar diversos asuntos relacionados con el data-binding en la librería. Son prácticas habituales que reunimos en un solo artículo porque resultan fáciles de entender y de explicar. Las usarás bastante para desarrollar todo tipo de componentes.

Veremos los operadores de negación en los bindeos, así como los bindings computados y los bindings compuestos, con ejemplos de componentes, que nos permitirán seguir practicando en el desarrollo de custom elements con Polymer 2. Pero recuerda que ya venimos de largo hablando sobre binding en artículos anteriores, por lo que, si necesitas aclarar conceptos, te recomendamos comenzar por el artículo Binding en Polymer 2.

Operador lógico de negación en binding

Este es un operador que podemos insertar en los bindings. Se usa el mismo carácter de negación de Javascript y sirve lógicamente para lo mismo que cualquier negación programática. El valor bindeado será la negación de aquella propiedad que se está usando.

[[! propiedad]]

Lo puedes usar siempre que lo necesites, pero un caso habitual es cuando tienes que mostrar unas veces un contenido y otras veces otro. En este caso mantienes una única propiedad y la usas dos veces, una de ellas negada. Ese ejemplo es el que hemos usado en el componente a continuación, que muestra un icono u otro en función de algo estar encendido o apagado.

<link rel="import" href="../../bower_components/polymer/polymer-element.html">
<link rel="import" href="../../bower_components/iron-icon/iron-icon.html">
<link rel="import" href="../../bower_components/iron-icons/iron-icons.html">

<dom-module id="encendido-apagado">
  <template>
    <style>
      :host {
        display: block
      }
    </style>

    <iron-icon hidden$="[[! encendido]]" icon="icons:cloud-done"></iron-icon>
    <iron-icon hidden$="[[encendido]]" icon="icons:cloud-off"></iron-icon>
  </template>

  <script>
    
    class EncendidoApagado extends Polymer.Element {
      static get is() {
        return 'encendido-apagado';
      }

      static get properties() {
        return {
          encendido: {
            type: Boolean,
            value: false
          }
        };
      }

      constructor() {
        super();
      }

    }

    window.customElements.define(EncendidoApagado.is, EncendidoApagado);
  </script>
</dom-module>

La clave de este componente está en los atributos hidden, que ocultan o muestran algo.

<iron-icon hidden$="[[! encendido]]" icon="icons:cloud-done"></iron-icon>
<iron-icon hidden$="[[encendido]]" icon="icons:cloud-off"></iron-icon>

El icono que indica apagado se tiene que ocultar cuando está encendido. El icono de encendido se tiene que ocultar cuando NO está encencido.

Nota: Puedes ver que estamos realizando un binding a atributo, para el atributo hidden, porque se utiliza el carácter "$". Si no lo conoces te recomendamos la lectura del artículo bindeo a propiedades vs bindeo a atributos.

Este componente se usará con el siguiente HTML:

<encendido-apagado></encendido-apagado>
<encendido-apagado encendido></encendido-apagado>

En uno de los casos, el primero, aparecerá el icono como apagado. En el segundo caso aparecerá como encendido.

Bindings computados (Computed bindings)

Hasta ahora hemos usado siempre binding por medio de propiedades del componente, indicando entre dobles corchetes o dobles llaves el nombre de la propiedad que se debe bindear en algún lugar. Sin embargo existe otra posibilidad muy útil del binding en Polymer 2 que consiste en usar funciones que nos devuelven el valor que debe bindearse. El nombre de esta utilidad es bindings computados, o computed bindings.

Las funciones son capaces de realizar un cálculo y mediante su return indicaremos aquello que debe bindear. Como te imaginaras, estas funciones se implementan por medio de métodos en la clase del componente.

Nota: podrías entender los computed bindings como un tipo especial de propiedades computadas, explicadas anteriormente. Solo que las propiedades computadas había que declararlas en las properties del elemento y los computed bindings se declaran en el template al definir el correspondiente binding.

Este podría ser un ejemplo de binding computado:

<div>
    Días festivos del mes: [[_diasFestivosMes(mes)]]
</div>

El método "_diasFestivosMes" recibe un mes (se supone que mes es una propiedad del componente). Usará ese mes para calcular los días festivos y devolverá el resultado que se deba mostrar. Cada vez que el valor de la propiedad "mes" cambie en el componente, el método "_diasFestivosMes" se volverá a invocar, devolviendo siempre el resultado actualizado, que a su vez provocará que se refresque el template con el nuevo valor calculado.

Vamos a ver a continuación un componente sencillo que hace uso de los bindings computados. Básicamente el componente aplica el IVA a un valor, para lo que necesita conocerse el valor sin impuestos y el tipo de IVA a aplicar. El propio valor más el IVA se calcula en función de los otros datos, por lo que podríamos hacer un binding computado para visualizarlo en el template del componente.

<link rel="import" href="../bower_components/polymer/polymer-element.html">

<dom-module id="aplicador-iva">
  <template>
    <style>
      :host {
        display: block
      }
    </style>

    <div>
      <span>Valor:</span> {{valor}}
    </div>
    <div>
      <span>Tipo IVA:</span> {{tipoIva}}
    </div>
    <div>
      <span>Valor con IVA:</span> [[_valorConIva(valor, tipoIva)]]
    </div>
  </template>

  <script>
    
    class AplicadorIva extends Polymer.Element {
      static get is() {
        return 'aplicador-iva';
      }

      static get properties() {
        return {
          valor: {
            type: Number,
            value: 0
          },
          tipoIva: {
            type: Number,
            value: 21
          }
        };
      }

      constructor() {
        super();
      }

      _valorConIva(valor, tipoIva) {
        return valor + (valor * tipoIva / 100);
      }
    }

    window.customElements.define(AplicadorIva.is, AplicadorIva);
  </script>
</dom-module>

En el caso de este componente, el binding computado se encontraba declarado en el template:

<span>Valor con IVA:</span> [[_valorConIva(valor, tipoIva)]]

El método "_valorConIva" se encarga de devolver el valor que debe mostrarse como consecuencia del computed binding. A ese método se le envían los valores "valor" y "tipoIva", que son propiedades del componente. Cada vez que las propiedades "valor" y "tipoIva" cambian se invoca de nuevo el método.

Para poder usar este componente tendrás que enviar los valores de las propiedades asignando atributos en el HTML:

<aplicador-iva valor="100"></aplicador-iva>
<aplicador-iva valor="50" tipo-iva="10"></aplicador-iva>

Sobre los computed bindings debes saber además:

  • Las dependencias del bindeo computado, que son los datos que necesita para calcular el valor a mostrar, serán derivados del scope (ámbito) actual. Esto quiere decir que dentro de un template de repetición (dom-repeat), el scope puede incluir la propiedad "item", que no es una propiedad del propio componente.
  • Puedes enviar literales de cualquier tipo a los métodos del binding computado.
  • Si un computed binding no tiene ningún parámetro ocurrirá que la invocación al método se realizará una única vez.

Bindings compuestos (Compound bindings)

Otra alternativa para utilizar en data binding son los bindings compuestos, que permiten concatenar una cadena con el valor de una propiedad.

Es algo muy útil, cuando la propiedad es la parte del valor que se desea bindear. Por ejemplo la URL de una imagen.

<img src$="https://desarrolloweb.com/avatares/[[identificadorUsuario]].jpg" alt="Avatar usuario">

En este caso el $ del src es necesario de usar porque estamos bindeando a un atributo, pero podrías bindear a una propiedad de componentes Polymer perfectamente.

<avatar-usuario imagen="https://desarrolloweb.com/avatares/[[identificadorUsuario]].jpg"></avatar-usuario>

Cada vez que cambie la propiedad "identificadorUsuario" cambiará el valor del binding compuesto. Además tienes que saber que en los bindings compuestos solo se puede hacer binding de 1 dirección, de modo que, aun usando dobles llaves el binding sería solo de padres hacia hijos.

Ten en cuenta que una propiedad con valor "undefined" será bindeada como una cadena vacía. Lo puedes ver usando un componente como el del siguiente ejemplo.

<link rel="import" href="../../bower_components/polymer/polymer-element.html">
<link rel="import" href="../../bower_components/iron-icon/iron-icon.html">
<link rel="import" href="../../bower_components/iron-icons/iron-icons.html">

<dom-module id="enlace-perfil">
  <template>
    <style>
      :host {
        display: block
      }
    </style>

    <a href$="https://www.escuela.it/profiles/[[idUsuario]]">Enlace a perfil</a>
  </template>

  <script>
    
    class EnlacePerfil extends Polymer.Element {
      static get is() {
        return 'enlace-perfil';
      }

      static get properties() {
        return {
          idUsuario: String
        };
      }

      constructor() {
        super();
      }

    }

    window.customElements.define(EnlacePerfil.is, EnlacePerfil);
  </script>
</dom-module>

La propiedad "idUsuario" no tiene un valor predeterminado, por lo que se crea con el valor "undefined". Si usas este componente sin indicar un identificador de usuario:

<enlace-perfil></enlace-perfil>

El bindeo compuesto te dará la URL https://www.escuela.it/profiles/

Ahora bien, si usas este componente cargando un identificador de usuario:

<enlace-perfil id-usuario="44"></enlace-perfil>

El enlace al perfil se cargará con la URL compuesta usando ese identificador https://www.escuela.it/profiles/44