Observers en Polymer 2. Parte 7

¿Que son los observers?

Es una característica de polymer que nos permiten saber si una propiedad se ha modificado.

A continuación un listado de los observers que vamos a ver en este módulo:

  • Observers simples
  • Observers complejos
  • Observers a subpropiedades
  • Observers array
  • Observers path

Observers simples

Para usar los observers simples tenemos que declarar directamente en las propiedades del componente como lo hacemos con type, value, computed, notify, etc…

Para declarar el observer simple usamos la palabra observer seguida del método que vamos a usar como observador.

Cuando esa propiedad se modifique se ejecutara el método declarado.

Cada vez que se modifica el valor, polymer automáticamente nos envía dos parámetros en el método que hemos declarando dentro del observer.

El primero seria el nuevo valor y el segundo valor el antiguo.

A continuación unos ejemplos para entenderlo mejor:

index.html

<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes">

  <title>observers-app</title>
  <meta name="description" content="observers-app description">

  <!-- See https://goo.gl/OOhYW5 -->
  <link rel="manifest" href="/manifest.json">

  <script src="/bower_components/webcomponentsjs/webcomponents-loader.js"></script>

  <link rel="import" href="/src/observers-app-app/observers-app-app.html">
</head>

<body>
  <observers-app-app nombre='test01'></observers-app-app>
</body>

</html>

observers-app-app.html

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

<dom-module id="observers-app-app">
  <template>
    <style>
      :host {
        display: block;
      }
    </style>
    <input type="text" value='{{nombre::input}}'>
    <h2>Observers [[nombre]]</h2>
    <p>Valor nuevo
      <div id='new'></div>
    </p>

    <p>Valor anterior
      <div id='old'></div>
    </p>

  </template>

  <script>
    /**
     * @customElement
     * @polymer
     */
    class ObserversAppApp extends Polymer.Element {
      static get is() { return 'observers-app-app'; }
      static get properties() {
        return {
          nombre: {
            type: String,
            value: '',
            observer: 'changeNombre'
          }
        };
      }
      changeNombre(newValue, oldValue) {
        this.$.new.innerHTML = newValue;
        this.$.old.innerHTML = oldValue;
      }
    }

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

Observers complejos

Con los observers simples podíamos saber si una propiedad se había modificado, pero con los observers complejos podemos observar varias propiedades a la vez.

Para declarar los observers múltiples necesitamos definir un método estático como is(), properties() y allí dentro devolver un array con los métodos  que se ejecutaran y las propiedades que estamos observando.

En los observers complejos solo recibimos el valor actual que se esta modificando.

A continuación el código con los observers complejos:

index.html

<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes">

  <title>observers-app</title>
  <meta name="description" content="observers-app description">

  <!-- See https://goo.gl/OOhYW5 -->
  <link rel="manifest" href="/manifest.json">

  <script src="/bower_components/webcomponentsjs/webcomponents-loader.js"></script>

  <link rel="import" href="/src/observers-app-app/observers-app-app.html">
</head>

<body>
  <observers-app-app nombre='test01'></observers-app-app>
</body>

</html>

observers-app-app.html

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

<dom-module id="observers-app-app">
  <template>
    <style>
      :host {
        display: block;
      }
    </style>
    <input type="text" value='{{nombre::input}}'>
    <h2>Observers [[nombre]]</h2>
    <p>Valor nuevo
      <div id='new'></div>
    </p>

    <p>Valor anterior
      <div id='old'></div>
    </p>
    <input type="text" value='{{val1::input}}' placeholder="val1">
    <input type="text" value='{{val2::input}}' placeholder="val2">
    <input type="text" value='{{val3::input}}' placeholder="val3">
  </template>

  <script>
    /**
     * @customElement
     * @polymer
     */
    class ObserversAppApp extends Polymer.Element {
      static get is() { return 'observers-app-app'; }
      static get properties() {
        return {
          nombre: {
            type: String,
            value: '',
            observer: 'changeNombre'
          },
          val1: {
            type: String,
            value: ''
          },
          val2: {
            type: String,
            value: ''
          },
          val3: {
            type: String,
            value: ''
          }
        };
      }
      static get observers() {
        return [
          'metodo1(val1, val2)', 'metodo2(val1, val3)', 'metodo3(val2, val3)', 'metodo4(val1,val2,val3)'
        ];
      }
      changeNombre(newValue, oldValue) {
        this.$.new.innerHTML = newValue;
        this.$.old.innerHTML = oldValue;
      }
      metodo1(v1, v2) {
        console.log("Metodo1 - val1 y val2", v1, v2);
      }
      metodo2(v1, v2) {
        console.log("Metodo2 - val1 y val3", v1, v2);
      }
      metodo3(v1, v2) {
        console.log("Metodo3 - val2 y val3", v1, v2);
      }
      metodo4(v1, v2, v3) {
        console.log("Metodo4 - val1, val2 y val3", v1, v2, v3);
      }
    }

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

Observers a subpropiedades

Si recordamos en el módulo de data binding, para poder hacer binding a las subpropiedades necesitábamos hacer set para que el data binding funcione, pues para los observers a subpropiedades es parecido.

Si nosotros tenemos un objeto y modificamos uno de sus valores, ese valor se modificara en el objeto pero no se ejecutara el binding, y no sabremos que esa subpropiedad del objeto a cambiado.

Para que esto funcione necesitamos usar observers complejos, y allí indicar con un método la subpropiedad que vamos a observar.

Como ocurre con el binding también necesitamos usar set para que funcione el binding y nos enteremos de ese cambio.

Si en algún momento modificásemos alguna propiedad directamente, sin usar un observersset, la subpropiedad se modificaría pero no ejecutaría el binding y no la veríamos reflejada.

A continuación el código con los observers a subpropiedades:

index.html

<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes">

  <title>observers-app</title>
  <meta name="description" content="observers-app description">

  <!-- See https://goo.gl/OOhYW5 -->
  <link rel="manifest" href="/manifest.json">

  <script src="/bower_components/webcomponentsjs/webcomponents-loader.js"></script>

  <link rel="import" href="/src/observers-app-app/observers-app-app.html">
</head>

<body>
  <observers-app-app nombre='test01'></observers-app-app>
</body>

</html>

observers-app-app.html

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

<dom-module id="observers-app-app">
  <template>
    <style>
      :host {
        display: block;
      }
    </style>
    <input type="text" value='{{nombre::input}}'>
    <h2>Observers [[nombre]]</h2>
    <p>Valor nuevo
      <div id='new'></div>
    </p>

    <p>Valor anterior
      <div id='old'></div>
    </p>
    <input type="text" value='{{val1::input}}' placeholder="val1">
    <input type="text" value='{{val2::input}}' placeholder="val2">
    <input type="text" value='{{val3::input}}' placeholder="val3">
    <p>SUBPROPIEDADES</p>
    <input type="text" value='{{datosObjeto.nombre::input}}' placeholder="Valor para subpropiedad">
    <div>
      <p>Datos objeto</p>
      <p>Nombre:[[datosObjeto.nombre]]</p>
      <p>Edad:[[datosObjeto.edad]]</p>
      <p>Estado:[[datosObjeto.estado]]</p>
      <button on-click='limpiar'>Limpiar nombre</button>
      <button on-click='limpiarSet'>Limpiar nombre SET</button>
    </div>
  </template>

  <script>
    /**
     * @customElement
     * @polymer
     */
    class ObserversAppApp extends Polymer.Element {
      static get is() { return 'observers-app-app'; }
      static get properties() {
        return {
          datosObjeto: {
            type: Object,
            value: function () {
              return {
                nombre: 'Test',
                edad: 100,
                estado: true
              }
            }
          },
          nombre: {
            type: String,
            value: '',
            observer: 'changeNombre'
          },
          val1: {
            type: String,
            value: ''
          },
          val2: {
            type: String,
            value: ''
          },
          val3: {
            type: String,
            value: ''
          }
        };
      }
      static get observers() {
        return [
          'metodo1(val1, val2)',
          'metodo2(val1, val3)',
          'metodo3(val2, val3)',
          'metodo4(val1,val2,val3)',
          'modificarSubpropiedad(datosObjeto.nombre)'
        ];
      }
      limpiar() {
        console.log(this.datosObjeto.nombre);
        // No funciona, solo se modifica el objeto
        this.datosObjeto.nombre = '';
        console.log(this.datosObjeto.nombre);
      }
      limpiarSet() {
        console.log(this.datosObjeto.nombre);
        // Aqui se modifica el objeto y ocurre el binding
        this.set('datosObjeto.nombre', '');
        console.log(this.datosObjeto.nombre);
      }
      modificarSubpropiedad(valor) {
        console.log("SUBPROPIEDAD", valor);
      }
      changeNombre(newValue, oldValue) {
        this.$.new.innerHTML = newValue;
        this.$.old.innerHTML = oldValue;
      }
      metodo1(v1, v2) {
        console.log("Metodo1 - val1 y val2", v1, v2);
      }
      metodo2(v1, v2) {
        console.log("Metodo2 - val1 y val3", v1, v2);
      }
      metodo3(v1, v2) {
        console.log("Metodo3 - val2 y val3", v1, v2);
      }
      metodo4(v1, v2, v3) {
        console.log("Metodo4 - val1, val2 y val3", v1, v2, v3);
      }
    }

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

Observers a arrays

Como en todas las modificaciones que estamos viendo es necesario usar los métodos que nos ofrece polymer para que se ejecute el binding.

Para definir un observer de un array lo haremos usando observers complejos.

Con este tipo de observer podemos saber si se modifica cualquier elemento dentro de un array.

La sintaxis para pode usarlo es definiendo un método dentro de un observer complejo, e indicar como parámetro el array.splices.

Con .splices le decimos a polymer que vamos a observar los cambios sobre cada elemento del array y no sobre el array entero.

Los métodos para la manipulación de arrays son los mismo que en javascript nativo (push, pop, splice, etc…), pero usando this.

El método que declaramos dentro del observers complejo y su parámetro con el array .splices, es un objeto bastante complejo que nos da mucha información.

A continuación un vídeo y algo de código:

index.html

<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes">

  <title>observers-app</title>
  <meta name="description" content="observers-app description">

  <!-- See https://goo.gl/OOhYW5 -->
  <link rel="manifest" href="/manifest.json">

  <script src="/bower_components/webcomponentsjs/webcomponents-loader.js"></script>

  <link rel="import" href="/src/observers-app-app/observers-app-app.html">
</head>

<body>
  <observers-app-app nombre='test01'></observers-app-app>
</body>

</html>

observers-app-app.html

<link rel="import" href="../../bower_components/polymer/polymer-element.html">
<link rel="import" href="../../bower_components/polymer/lib/elements/dom-repeat.html">
<dom-module id="observers-app-app">
  <template>
    <style>
      :host {
        display: block;
      }
    </style>
    <input type="text" value='{{nombre::input}}'>
    <h2>Observers [[nombre]]</h2>
    <p>Valor nuevo
      <div id='new'></div>
    </p>

    <p>Valor anterior
      <div id='old'></div>
    </p>
    <input type="text" value='{{val1::input}}' placeholder="val1">
    <input type="text" value='{{val2::input}}' placeholder="val2">
    <input type="text" value='{{val3::input}}' placeholder="val3">
    <p>SUBPROPIEDADES</p>
    <input type="text" value='{{datosObjeto.nombre::input}}' placeholder="Valor para subpropiedad">
    <div>
      <p>Datos objeto</p>
      <p>Nombre:[[datosObjeto.nombre]]</p>
      <p>Edad:[[datosObjeto.edad]]</p>
      <p>Estado:[[datosObjeto.estado]]</p>
      <button on-click='limpiar'>Limpiar nombre</button>
      <button on-click='limpiarSet'>Limpiar nombre SET</button>

      <p>Observers ARRAY</p>
      <button on-click='addHeroe'>ADD Array</button>
      <ul>
        <template is='dom-repeat' items='[[arraySuperHeroes]]' as='heroe'>
          <li>[[heroe]]
            <button on-click='deleteHeroe' id='[[index]]'>DELETE [[heroe]] Array</button>
          </li>
        </template>
      </ul>
    </div>
  </template>

  <script>
    /**
     * @customElement
     * @polymer
     */
    class ObserversAppApp extends Polymer.Element {
      static get is() { return 'observers-app-app'; }
      static get properties() {
        return {
          datosObjeto: {
            type: Object,
            value: function () {
              return {
                nombre: 'Test',
                edad: 100,
                estado: true
              }
            }
          },
          arraySuperHeroes: {
            type: Array,
            value: function () {
              return ['Spiderman', 'Batman', 'Superman', 'Hulk', 'Thor', 'Ironman'];
            }
          },
          nombre: {
            type: String,
            value: '',
            observer: 'changeNombre'
          },
          val1: {
            type: String,
            value: ''
          },
          val2: {
            type: String,
            value: ''
          },
          val3: {
            type: String,
            value: ''
          }
        };
      }
      static get observers() {
        return [
          'metodo1(val1, val2)',
          'metodo2(val1, val3)',
          'metodo3(val2, val3)',
          'metodo4(val1,val2,val3)',
          'modificarSubpropiedad(datosObjeto.nombre)',
          'arrayHeroes(arraySuperHeroes.splices)'
        ];
      }
      addHeroe() {
        this.push('arraySuperHeroes', Math.floor(Math.random() * 10));
      }
      deleteHeroe(evt) {
        this.splice('arraySuperHeroes', parseInt(evt.target.id), 1);
      }
      arrayHeroes(arr) {
        console.log(arr);
      }
      limpiar() {
        console.log(this.datosObjeto.nombre);
        // No funciona, solo se modifica el objeto
        this.datosObjeto.nombre = '';
        console.log(this.datosObjeto.nombre);
      }
      limpiarSet() {
        console.log(this.datosObjeto.nombre);
        // Aqui se modifica el objeto y ocurre el binding
        this.set('datosObjeto.nombre', '');
        console.log(this.datosObjeto.nombre);
      }
      modificarSubpropiedad(valor) {
        console.log("SUBPROPIEDAD", valor);
      }
      changeNombre(newValue, oldValue) {
        this.$.new.innerHTML = newValue;
        this.$.old.innerHTML = oldValue;
      }
      metodo1(v1, v2) {
        console.log("Metodo1 - val1 y val2", v1, v2);
      }
      metodo2(v1, v2) {
        console.log("Metodo2 - val1 y val3", v1, v2);
      }
      metodo3(v1, v2) {
        console.log("Metodo3 - val2 y val3", v1, v2);
      }
      metodo4(v1, v2, v3) {
        console.log("Metodo4 - val1, val2 y val3", v1, v2, v3);
      }
    }

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

Observers path

Los últimos observers que estamos viendo son casi todos basados en observers complejos, que realmente cambian muy poco y que funcionan muy bien en objetos y arrays.

Para ejecutar un observers cuando cambia cualquier subpropiedad de un objeto o un array, debemos usar el carácter ‘*‘.

Con esto especificamos que vamos observar todas las subpropiedades que cuelgan desde ese punto.

El método que usamos para observar recibirá un objeto con las siguientes propiedades:

  • path: Ruta a la subpropiedad que cambio.
  • value: Nuevo valor que ha cambiado.
  • base: El objeto que coincide con la parte que no tiene comodín en la ruta.

Ahora un poco de código:

index.html

<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes">

  <title>observers-app</title>
  <meta name="description" content="observers-app description">

  <!-- See https://goo.gl/OOhYW5 -->
  <link rel="manifest" href="/manifest.json">

  <script src="/bower_components/webcomponentsjs/webcomponents-loader.js"></script>

  <link rel="import" href="/src/observers-app-app/observers-app-app.html">
</head>

<body>
  <observers-app-app nombre='test01'></observers-app-app>
</body>

</html>

observers-app-app.html

<link rel="import" href="../../bower_components/polymer/polymer-element.html">
<link rel="import" href="../../bower_components/polymer/lib/elements/dom-repeat.html">
<dom-module id="observers-app-app">
  <template>
    <style>
      :host {
        display: block;
      }
    </style>
    <input type="text" value='{{nombre::input}}'>
    <h2>Observers [[nombre]]</h2>
    <p>Valor nuevo
      <div id='new'></div>
    </p>

    <p>Valor anterior
      <div id='old'></div>
    </p>
    <input type="text" value='{{val1::input}}' placeholder="val1">
    <input type="text" value='{{val2::input}}' placeholder="val2">
    <input type="text" value='{{val3::input}}' placeholder="val3">
    <p>SUBPROPIEDADES</p>
    <input type="text" value='{{datosObjeto.nombre::input}}' placeholder="Valor para subpropiedad">
    <div>
      <p>Datos objeto</p>
      <p>Nombre:[[datosObjeto.nombre]]</p>
      <p>Edad:[[datosObjeto.edad]]</p>
      <p>Estado:[[datosObjeto.estado]]</p>
      <button on-click='limpiar'>Limpiar nombre</button>
      <button on-click='limpiarSet'>Limpiar nombre SET</button>

      <p>Observers ARRAY</p>
      <button on-click='addHeroe'>ADD Array</button>
      <ul>
        <template is='dom-repeat' items='[[arraySuperHeroes]]' as='heroe'>
          <li>[[heroe]]
            <button on-click='deleteHeroe' id='[[index]]'>DELETE [[heroe]] Array</button>
          </li>
        </template>
      </ul>
      
      <p>Observers Path</p>
      <div>
        <input type="text" placeholder="datosPersonales_nombre" value="{{datosPersonales.nombre::input}}">
        <input type="text" placeholder="datosPersonales_apellidos" value="{{datosPersonales.apellidos::input}}">
        <input type="text" placeholder="datosPersonales_otros_altura" value="{{datosPersonales.otros.altura::input}}">
        <input type="text" placeholder="datosPersonales_otros_edad" value="{{datosPersonales.otros.edad::input}}">
      </div>
    </div>
  </template>

  <script>
    /**
     * @customElement
     * @polymer
     */
    class ObserversAppApp extends Polymer.Element {
      static get is() { return 'observers-app-app'; }
      static get properties() {
        return {
          datosPersonales: {
            type: Object,
            value: function () {
              return {
                nombre: '',
                apellidos: '',
                otros: {
                  type: Object,
                  value: function () {
                    return {
                      altura: Number,
                      edad: Number,
                      peso: Number
                    }
                  }
                }
              }
            }
          },
          datosObjeto: {
            type: Object,
            value: function () {
              return {
                nombre: 'Test',
                edad: 100,
                estado: true
              }
            }
          },
          arraySuperHeroes: {
            type: Array,
            value: function () {
              return ['Spiderman', 'Batman', 'Superman', 'Hulk', 'Thor', 'Ironman'];
            }
          },
          nombre: {
            type: String,
            value: '',
            observer: 'changeNombre'
          },
          val1: {
            type: String,
            value: ''
          },
          val2: {
            type: String,
            value: ''
          },
          val3: {
            type: String,
            value: ''
          }
        };
      }
      static get observers() {
        return [
          'metodo1(val1, val2)',
          'metodo2(val1, val3)',
          'metodo3(val2, val3)',
          'metodo4(val1,val2,val3)',
          'modificarSubpropiedad(datosObjeto.nombre)',
          'arrayHeroes(arraySuperHeroes.splices)',
          'pathObservers(datosPersonales.otros.*)'
        ];
      }
      pathObservers(obj) {
        debugger;
        console.log(obj);
      }
      addHeroe() {
        this.push('arraySuperHeroes', Math.floor(Math.random() * 10));
      }
      deleteHeroe(evt) {
        this.splice('arraySuperHeroes', parseInt(evt.target.id), 1);
      }
      arrayHeroes(arr) {
        console.log(arr);
      }
      limpiar() {
        console.log(this.datosObjeto.nombre);
        // No funciona, solo se modifica el objeto
        this.datosObjeto.nombre = '';
        console.log(this.datosObjeto.nombre);
      }
      limpiarSet() {
        console.log(this.datosObjeto.nombre);
        // Aqui se modifica el objeto y ocurre el binding
        this.set('datosObjeto.nombre', '');
        console.log(this.datosObjeto.nombre);
      }
      modificarSubpropiedad(valor) {
        console.log("SUBPROPIEDAD", valor);
      }
      changeNombre(newValue, oldValue) {
        this.$.new.innerHTML = newValue;
        this.$.old.innerHTML = oldValue;
      }
      metodo1(v1, v2) {
        console.log("Metodo1 - val1 y val2", v1, v2);
      }
      metodo2(v1, v2) {
        console.log("Metodo2 - val1 y val3", v1, v2);
      }
      metodo3(v1, v2) {
        console.log("Metodo3 - val2 y val3", v1, v2);
      }
      metodo4(v1, v2, v3) {
        console.log("Metodo4 - val1, val2 y val3", v1, v2, v3);
      }
    }

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