Ejemplo de uso de reflectToAttribute en Polymer 2

  • Por
En este artículo vamos a mostrar un ejemplo que nos ilustre el uso de la configuración reflectToAttribute en componentes de Polymer 2.

En un pasado artículo del Manual de Polymer explicamos de manera general el listado de posibles configuraciones en propiedades de Polymer 2. Vimos que hay muchas posibilidades y algunas de ellas quedaron pendientes de una explicación más detallada.

Ahora vamos a practicar con una de las configuraciones de propiedad más simples, reflectToAttribute. Como hemos mencionado anteriormente, sirve para que la etiqueta host refleje en sus atributos los cambios que se produzcan en las propiedades del componente. Recuerda, el atributo es lo que escribimos en una etiqueta HTML y la propiedad es lo que controlamos desde Javascript.

Podemos tener un componente que sea así:

<componente-complejo></componente-complejo>

Al usarlo no indicamos ningún atributo, pero quizás dentro del componente complejo tenemos decenas de propiedades para controlar su estado. Esas propiedades generalmente se gestionan dentro del componente y los valores actuales no se reflejan modificando los atributos en la etiqueta host.

Nota: Por etiqueta host puedes entender la etiqueta que escribes para usar un componente. Dentro del componente podemos tener un template con una serie de etiquetas en lo que llamamos el Shadow DOM, pero el componente en si se usa con una sola etiqueta, con el nombre del elemento. Esa etiqueta que se coloca en el HTML para usar el componente es la etiqueta host.

Ejemplo de componente con propiedades reflectToAttribute

Entendido esto, podemos pasar a ver el uso de la configuración reflectToAttribute, que modifica el comportamiento del componente para reflejar el estado de las propiedades que deseemos que se vean desde fuera.

Para poner en marcha esta funcionalidad hemos creado este elemento:

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

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

    Este es el valor que debe verse en el atributto del host: [[reflejada]]
    <button on-click="cambiarValor">Haz clic para cambiar el valor (que se reflejará en el atributo)</button>
  </template>

  <script>

    class TipoPersonalizado {
      //aquí el código de un nuevo tipo inventado
    }
    
    class UsoReflecttoattribute extends Polymer.Element {
      
      static get is() {
        return 'uso-reflecttoattribute';
      }

      static get properties() {
        return {
          reflejada: {
            type: String,
            value: 'Me reflejo!!',
            reflectToAttribute: true
          }
        };
      }

      constructor() {
        super();
      }

      cambiarValor() {
        this.reflejada = Date.now();
      }
    }

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

Ahora, para usar el componente podríamos tener perfectamente este HTML:

<uso-reflecttoattribute></uso-reflecttoattribute>

Si lo ponemos en marcha, cuando el componente sufre el upgrade, veremos cómo en el instante aparece el atributo "reflejada" con su valor predeterminado. Al inspeccionar el HTML con las herramientas de desarrolladores veríamos esto (fíjate la etiqueta host "uso-reflecttoattribute"):

El componente además tiene un botón que, al pulsarse, cambia el estado de la propiedad, con lo que veríamos que el nuevo estado también se refleja como atributo en la etiqueta host.

Serialización de las propiedades reflejadas con reflectToAttribute

Es interesante saber que, cuando se refleja una propiedad en un atributo del componente ocurre una serialización del valor de la propiedad. Esa serialización se realiza para convertir aquel valor de la propiedad en un string, ya que los valores de los atributos en HTML solo aceptan cadenas.

Nota: este paso, la serialización de las propiedades reflejadas, es básicamente el opuesto de la deserialización que hemos detallado en el artículo sobre Tipos de datos en deserialización de atributos.

Al serializar se realizan unas operaciones de conversión a cadena que dependen del tipo (type) de la propiedad declarada. Por ejemplo un Date o Number se serializan con el método toString() o el array y object se serializan con JSON.stringify(). Sin embargo nosotros podemos alterar la serialización predeterminada o crear propiedades de tipos personalizados que tengan serializaciones determinadas por nuestro propio código. Para ello tenemos que sobreescribir el método _serializeValue(value) en nuestro componente.

A continuación puedes ver un ejemplo de sobreescritura del método:

_serializeValue(value) {
  if (typeof(value) == 'number') {
    return '--' + value + '--';
  }
  if (value instanceof Cliente) {
    return value.nombreContacto + ' de ' + value.empresa + ' (' + value.cif + ')'; 
  }
  return super._serializeValue(value);
}

En este ejemplo encontramos un primer condicional if que detecta si se debe serializar una propiedad de tipo Number, en cuyo caso no se usará la serialización predeterminada de Polymer, sino una personalizada por nosotros mimos. Como puedes apreciar, lo que se devuelve es una cadena con el número embutido entre dos guiones.

En el segundo condicional se detecta si lo que se tiene que serializar es un objeto de la clase Cliente, en cuyo caso se hace una serialización personalizada, en una cadena en la que se visualizan los datos del cliente.

Por último, para cualquier otro tipo de dato, se llama al método de serialización de la clase padre.

Este ejemplo lo puedes ver en uso en el siguiente componente.

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

<dom-module id="uso-reflecttoattribute-serialize">
  <template>
    <style>
      :host {
        display: block
      }
    </style>

  </template>

  <script>

    class Cliente {
      constructor(nombre, empresa, cif) {
        this.nombreContacto = nombre;
        this.empresa = empresa;
        this.cif = cif;
      }
    }
    
    class UsoReflecttoattributeSerialize extends Polymer.Element {
      
      static get is() {
        return 'uso-reflecttoattribute-serialize';
      }

      static get properties() {
        return {
          numeroReflejado: {
            type: Number,
            value: 992,
            reflectToAttribute: true            
          },
          cliente: {
            type: Cliente,
            value: function() {
              return new Cliente('Miguel Angel Alvarez', 'EscuelaIT', 'B99222000');
            },
            reflectToAttribute: true                        
          }
        };
      }

      constructor() {
        super();
      }

      cambiarValor() {
        this.reflejada = Date.now();
      }

      _serializeValue(value) {
        if (typeof(value) == 'number') {
          return '--' + value + '--';
        }
        if (value instanceof Cliente) {
          return value.nombreContacto + ' de ' + value.empresa + ' (' + value.cif + ')'; 
        }
        return super._serializeValue(value);
      }
    }

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