Observers múltiples con comodines Polymer 2

  • Por
Como hacer observers donde usamos comodines, de modo que podemos ejecutar código cuando cambia cualquier propiedad profunda de un objeto.

Lo que vamos a ver en este artículo es novedad en Polymer 2 y nos puede ahorrar mucho código, ya que ahora podemos declarar observers que están al tanto de caminos profundos configurables con comodines.

Son un tipo especial de observers complejos que analizan propiedades profundas, de los cuales ya hemos hablado en el Manual de Polymer 2. La novedad ahora es que, por medio de una única declaración de un observer, podemos controlar los cambios en un conjunto de subpropiedades y no en una única subpropiedad. Para definir este observer podremos usar el carácter * como comodín, de modo que nos afecte no solo a una propiedad, sino a todo el conjunto de subpropiedades.

Nota: Aquí cuando menciono subpropiedad es lo mismo que una propiedad profunda. En los componentes tenemos propiedades, que se declaran en "properties". Algunas de las propiedades del componente pueden ser objetos, que a su vez tienen propiedades. Las propiedades del objeto son las propiedades profundas o las subpropiedades. Por ejemplo, "calificacion" puede ser una propiedad de un componente, pero si su valor es un objeto puedes tener "calificacion.matematicas", "calificacion.lengua". En este caso, "matematicas" o "lengua" son las subpropiedades o propiedades profundas.

Declarar un observer con comodín

En un componente podemos tener una propiedad y a su vez esta propiedad tener subpropiedades.

static get properties() {
  return {
    calificacion: {
      type: Object,
      value: function() {
        return {
          matematicas: 5,
          lenguaje: 5,
          ciencias: 5
        };
      }
    }
  };
}

Si queremos observar, en un único observer, todos los cambios en cualquiera de sus subpropiedades, entonces podemos definir un único observer usando el comodín.

static get observers() {
  return [
    'cambiosCalificacion(calificacion.*)'
  ];
}

Cada vez que cambie cualquiera de las calificaciones en este componente, se invocará al método "cambiosCalificacion".

Recepción de los datos del cambio ocurrido

Como siempre ocurre en los observadores de Polymer, el método de implementación recibe los datos que se acaban de alterar. Sin embargo, con el uso del comodín, ocurre una cosa y es que no sabemos a priori cuál de las subpropiedades ha cambiado.

Para darnos los datos precisos sobre qué propiedad ha cambiado y el nuevo valor, Polymer nos envía un parámetro en el método del observador. Este parámetro es un objeto que contiene un resumen de los cambios que ha ocurrido en el elemento que estás observando.

Este objeto que recibes contendrá los siguientes datos:

  • path: Es el camino de lo que acaba de ser modificado. Te sirve para conocer cuál de las subpropiedades observadas se ha alterado.
  • value: Este es el nuevo valor que se ha asignado a la propiedad alterada.
  • base: Es el objeto completo que se está observando. Por ejemplo si observamos calificacion.*, lo que nos llegará es el objeto completo "calificacion", con todas sus subpropiedades.

Este podría ser el método de implementación del observer, que recibe el objeto que se acaba de describir.

cambiosCalificacion(changeRecord) {
  console.log('Ha cambiado ', changeRecord.path);
  console.log('El nuevo valor es ', changeRecord.value);
}

Ya dependiendo de tu código y lo que necesites hacer, puede que te interesen unas propiedades u otras del objeto recibido por parámetro, llamado changeRecord en el código anterior. Puede que incluso no lo necesites para nada si solo te interesa saber que ha cambiado alguna de sus subpropiedades pero te da igual cuál de ellas.

Nota: si aquello que ha cambiado es una casilla de un array, entonces en el campo "value" del objeto de registro de cambios recibiremos el resumen de los cambios en el array, por medio del objeto de "splices", lo que hemos descrito en el artículo de Observers sobre elementos de arrays.

Ejemplo de uso de observers con comodines

Ahora veamos el código de un componente que usa lo que acabamos de aprender. Es un componente que controla las calificaciones de notas de un estudiante. El componente calcula la media de la nota que el estudiante ha obtenido y dice si está aprobado o no. Pero ojo, para que el estudiante esté aprobado debe de tener calificaciones mayores que 5 en todas las asignaturas. Solo con que una de ellas esté suspendida, su calificación global será de suspenso.

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

<dom-module id="calificaciones-estudiante">
  <template>
    <style>
      :host {
        display: block
      }
      h1 {
        margin: 5px 0;
        font-size: 1.1em;
      }
    </style>
    <h1>Matemáticas</h1>
    Seleccionar calificación: <select value="{{calificacion.matematicas::change}}">
      <option value="0">0</option>
      <option value="1">1</option>
      <option value="2">2</option>
      <option value="3">3</option>
      <option value="4">4</option>
      <option value="5">5</option>
      <option value="6">6</option>
      <option value="7">7</option>
      <option value="8">8</option>
      <option value="9">9</option>
      <option value="10">10</option>
    </select>
    <h1>Lenguaje</h1>
    Seleccionar calificación: <select value="{{calificacion.lenguaje::change}}">
      <option value="0">0</option>
      <option value="1">1</option>
      <option value="2">2</option>
      <option value="3">3</option>
      <option value="4">4</option>
      <option value="5">5</option>
      <option value="6">6</option>
      <option value="7">7</option>
      <option value="8">8</option>
      <option value="9">9</option>
      <option value="10">10</option>
    </select>
    <h1>Ciencias naturales</h1>
    Seleccionar calificación: <select value="{{calificacion.ciencias::change}}">
      <option value="0">0</option>
      <option value="1">1</option>
      <option value="2">2</option>
      <option value="3">3</option>
      <option value="4">4</option>
      <option value="5">5</option>
      <option value="6">6</option>
      <option value="7">7</option>
      <option value="8">8</option>
      <option value="9">9</option>
      <option value="10">10</option>
    </select>
    <h1>MEDIA</h1>
    Media de las notas: [[media]]
    <p hidden$="[[!aprobado]]">Curso aprobado</p>
    <p hidden$="[[aprobado]]">Curso suspendido</p>

  </template>

  <script>
    class CalificacionesEstudiante extends Polymer.Element {

      static get is() {
        return 'calificaciones-estudiante';
      }

      static get properties() {
        return {
          media: Number,
          aprobado: {
            type: Boolean,
            value: true
          },
          calificacion: {
            type: Object,
            value: function() {
              return {
                matematicas: 5,
                lenguaje: 5,
                ciencias: 5
              };
            }
          }
        };
      }

      static get observers() {
        return [
          'cambiosCalificacion(calificacion.*)'
        ];
      }

      cambiosCalificacion(changeRecord) {
        let suma = 0;
        let materias = 0;
        let estaAprobado = true;
        for (let i in this.calificacion) {
          materias++;
          let calificacionActual = parseInt(this.calificacion[i]);
          suma += calificacionActual;
          if(calificacionActual < 5) {
            estaAprobado = false;
          }
        }
        this.media = suma / materias;
        this.media = Math.round(this.media * 10) / 10;
        this.aprobado = estaAprobado;
      }

      constructor() {
        super();
      }

    }

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

Todas las notas de las asignaturas, las calificaciones, se almacenan en la misma propiedad "calificacion". Esa propiedad del componente tiene a su vez un conjunto de subpropiedades, o propiedades profundas, una para cada asignatura. El bindeo a las subpropiedades se realiza cuando se produce un cambio en el campo SELECT.

Nota: Aunque no es materia de este artículo queremos explicar brevemente un asunto. En este caso verás un caso especial de binding. Resulta que el binding sobre elementos nativos de formularios no es tan sencillo que sobre componentes de Polymer. En este caso estamos seleccionando la nota de cada asignatura con un campo de formulario SELECT y su "value" no se puede bindear tan fácilmente a una propiedad del componente. Para facilitar ese bindeo en elementos nativos de formulario en Polymer se realiza una sintaxis especial, en la que se indica el nombre de la propiedad a la que se bindea y el tipo de evento que tiene que ocurrir en el elemento nativo para ejecutar ese enlace. En este caso concreto, cada vez que ocurre un "change" sobre un campo select (cambia su valor) se está alterando la propiedad del componente.

En nuestro componente hemos declarado un observer con comodín, para implementar en un único método el comportamiento a producirse cuando cambia la calificación de una asignatura.

En el método que implementa el observador se hace la tarea de recalcular la media de notas del estudiante y si está o no está aprobado de manera global. Si te fijas, aunque estamos recibiendo en este método el parámetro "changeRecord" realmente no me hace falta usarlo para nada. Esto puede ocurrir perfectamente.

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