Observers sencillos en Polymer 2

  • Por
Qué son los observers y cómo declarar observers sencillos sobre propiedades individuales, con ejemplos de desarrollo de componentes.

En este artículo del Manual de Polymer 2 vamos a conocer otra de las herramientas fundamentales para el desarrollo con Polymer 2: los observers, o en español observadores. Básicamente los observers son mecanismos mediante los cuales Polymer puede estar atento a los cambios sobre propiedades, de modo que si éstas cambian, se pueda reaccionar ejecutando ciertas funciones. Son, con las propiedades computadas, las herramientas más útiles para conseguir la programación reactiva, mediante la cual podemos escribir código que sabemos se ejecutará cuando ciertos sucesos ocurran.

Los sucesos que detectan los observadores son esencialmente cambios, que pueden ocurrir sobre propiedades simples, conjuntos de propiedades y en el caso de los arrays o de los objetos, de caminos determinados. Con ellos conseguimos que Polymer esté pendiente de cambios que puedan surgir y ejecute automáticamente las funciones que nosotros queramos cuando se detecten esos cambios.

Los observers más simples son los que vamos a ver en este artículo con ejemplos, los que ocurren sobre propiedades simples.

Cómo definir un observer en una propiedad individual

A la hora de declarar una propiedad podemos configurar diversos comportamientos, que hemos descrito en el artículo de Funcionalidades incorporadas en las propiedades de Polymer 2. Uno de ellos es justamente el observer que se puede asociar a esa propiedad.

La sintaxis para definir un observer sobre una propiedad es la siguiente:

static get properties() {
  return {
    propiedad: {
      type: String,
      observer: 'funcionObservadora'
    },
  };
}

El observer se configura indicando el nombre del método que se va a ejecutar cuando se detecten cambios en la propiedad. La invocación será automática y correrá por cuenta de Polymer sin que tengamos que intervenir.

El método observador recibirá como parámetro ciertos valores para facilitar el trabajo, que son básicamente el valor actual de la propiedad y el valor que tenía justo antes del cambio detectado.

funcionObservadora(valorActual, valorAnterior) {
  // Código del observer
}

Ya el código del observador permitirá ejecutar aquellas acciones necesarias ante los cambios y tendremos disponibles esos valores, actual y anterior, recibidos por los parámetros, así como el acceso a cualquier otra propiedad o método del componente que necesitemos acceder.

Ejemplo de observers en un componente que comprueba la validez de un email

Existen casos infinitos donde nos pueden venir muy bien los observers. Nosotros vamos a realizar uno sencillo, en un componente que se encarga de comprobar la validez de un email.

Básicamente el componente se alimentará de un valor tipo cadena de email que se le entregará por medio de un atributo en el componente y mostrará un icono cuando la cadena corresponda con un email sintácticamente válido y otro cuando la cadena no sea un email valido.

Elementos para trabajar con iconos

Antes de comenzar tenemos que explicar que en este ejercicio nos hemos apoyado en componentes de Polymer para trabajar con iconos que no hemos visto todavía. Estos componentes nos ofrecen una manera muy cómoda de trabajar con iconos y una buena biblioteca con cientos de imágenes vectoriales muy útiles en muchos tipos de aplicaciones.

Para trabajar con los iconos tendremos que instalar en nuestro proyecto dos nuevos elementos personalizados de la biblioteca de Polymer. Lo hacemos de manera similar a lo que ya hemos contado en el artículo de Instalar Polymer 2.

El primero de los componentes es el iron-icon, que es el que nos sirve para mostrar cualquier icono.

Lo instalas con el comando:

bower install --save PolymerElements/iron-icon

El segundo son las bibliotecas de iconos vectoriales que tienes en https://www.webcomponents.org/element/PolymerElements/iron-icons y que instalas con el comando:

bower install --save PolymerElements/iron-icons

Una vez instalados tenemos que asegurarnos de hace los correspondientes imports.

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

Y con ello ya podemos usar los iconos como por ejemplo:

<iron-icon icon="icons:check-circle">
Nota: iron-icons tiene como varias clasificaciones de iconos. La general la tienes en el import de iron-icons.html, pero luego tienes por ejemplo hardware-icons.html o device-icons.html, etc. para obtener iconos de otras clasificaciones.

Componente mostrar-validacion-email

Ahora vamos con la parte más relevante, la creación del componente. En este caso vamos a tener dos propiedades, una es el email, que no inicializamos porque se supone que ese email nos lo pasarán vía atributo al usar el componente. La otra propiedad es el icono que se debe mostrar, que cambia si el email es válido o no.

La propiedad donde colocaremos el observador es la del email, de modo que cada vez que cambia se ejecute un método como respuesta.

static get properties() {
  return {
    email: {
      type: String,
      observer: 'comprobarEmail'
    },
    icono: {
      type: String,
      value: "icons:clear"
    }
  };
}

Ahora podemos ver el método configurado como observer.

comprobarEmail(email) {
  if(this.validateEmail(email)) {
    this.icono = 'icons:check';
  } else {
    this.icono = 'icons:clear';
  }
}

Este método recibe el email, que es el nuevo email, con el valor al que acaba de cambiar. En el método se hacen las comprobaciones oportunas y con ellas se edita el icono que se debe mostrar para representar si el email era o no correcto.

Nota: si te acuerdas de las propiedades computed, quizás te des cuenta que este ejercicio se podría haber resuelto también con ellas como alternativa, haciendo la propiedad del icono como computada. Son dos vías para resolver un mismo problema, algunas veces será más cómodo trabajar con observadores y otra te resultará más adecuado trabajar con computadas. Los dos son programación reactiva y resultan muy útiles de usar.

Podemos ver el código del componente completo para salir de posibles dudas de implementación.

<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="marcar-email-valido">
  <template>
    <style>
      :host {
        display: inline;
      }
    </style>

    <iron-icon icon="[[icono]]">

  </template>

  <script>

    class MarcarEmailValido extends Polymer.Element {
      static get is() {
        return 'marcar-email-valido';
      }

      static get properties() {
        return {
          email: {
            type: String,
            observer: 'comprobarEmail'
          },
          icono: {
            type: String,
            value: "icons:clear"
          }
        };
      }

      constructor() {
        super();
      }

      comprobarEmail(email,e2, e3) {
        if(this.validateEmail(email)) {
          this.icono = 'icons:check';
        } else {
          this.icono = 'icons:clear';
        }
      }

      validateEmail(email) {
          var re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
          return re.test(email);
      }
    }

    window.customElements.define(MarcarEmailValido.is, MarcarEmailValido);
  </script>
</dom-module>
Nota: la función de validación del email la he sacado de stackoverflow.

Dependencias del observer

Llamamos las dependencias del observador a aquellas propiedades que son observadas, provocando la invocación de los métodos observadores cuando sus valores cambian.

En los observadores sencillos solo se declara una única dependencia, la propiedad única que está siendo observada. Si quieres observar más de una propiedad al mismo tiempo, entonces debes trabajar con observadores complejos, de los cuales vamos a hablar en los próximos artículos.

No obstante, cabe aclarar ya, a modo de recomendación, que en el código de los métodos observadores no es buena idea confiar en propiedades del componente que no formen parte de las dependencias del observador.

Por ejemplo, mira esta declaración de propiedades:

static get properties() {
  return {
    valorSinImpuestos: {
      type: Number,
      observer: 'valorCambiado'
    },
    tipoIVA: {
      type: Number
    }
  }
}

Ahora mira este observer, que no sería muy recomendable de implementar:

// Ojo!! cuidado con este código, pues es un ANTIPATRÓN
valorCambiado(nuevoValor, valorAnterior) {
  var valorConImpuestos = nuevoValor + (nuevoValor * this.tipoIVA) / 100;
}

El problema con el método anterior, motivo por el cual se considera un antipatrón, es que, si cambia la propiedad "tipoIVA", el método observador no se invocará, pues "tipoIVA" no pertenece como dependencias a este observador. Además, Polymer no nos asegura el orden con el que se inicializan las propiedades, por lo que podría ocurrir que, al ejecutarse este método observador, el valor de "tipoIVA" sea "undefined".

Lo correcto sería tener un observer complejo, en el que podamos acompañar dos propiedades a la vez. Esto lo veremos más adelante.

De momento es todo. Insistimos que solo hemos hablado de los observadores más sencillos. Más adelante explicaremos a trabajar con casos otra manera de declarar los observers, más compleja y versátil, también muy usada en Polymer y Polymer 2.