Mixins en Polymer 2. Parte 9

Introducción

Los mixins sustituyen a los behaviors de Polymer 1. 

Bien, ¿y que es un behavior en Polymer 1?. Pues los behaviors en Polymer 1 se usan para compartir código entre componentes de Polymer.

Bien una vez entendido esto, vamos a estructurar que vamos a ver en este módulo de mixins en Polymer 2:

  • Que es un mixin
  • Como usar un mixin
  • Como crear un mixin
  • Compartir un mixin

Ya que la idea de los mixins es compartir código entre componentes, vamos a explicar como podemos extender los elementos a través de la herencia de clases y poder genera componentes extendidos en base a otros.

Si nos fijamos, esta extensión o herencia lo hacemos siempre que creamos un nuevo componente al extender de Polymer:

class ComponentePopup extends Polymer.Element { }

Para comprobar como funciona mejor la extensión de los elementos vamos a crear un componente muy simple y vamos a extender (heredar) de el.

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>app-extend</title>
    <meta name="description" content="app-extend 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/app-extend-app/app-extend-app.html">
  </head>
  <body>
    <app-extend-app></app-extend-app>
  </body>
</html>

app-extend-app.html (componente que heredara del siguiente)

<link rel="import" href="../../bower_components/polymer/polymer-element.html">
<link rel="import" href="../text-extend/text-extend.html">
<dom-module id="app-extend-app">
  <template>
    <style>
      :host {
        display: block;
      }
    </style>
    <button on-click="saludo">Saludo</button>
    <button on-click="despedida">Despedida</button>
  </template>

  <script>
    /**
     * @customElement
     * @polymer
     */
    class AppExtendApp extends TextExtend {
      static get is() { return 'app-extend-app'; }
      static get properties() {
        return {

        };
      }
    }

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

text-extend.html

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

<dom-module id="text-extend">
  <script>
    class TextExtend extends Polymer.Element {
      static get is() { return 'text-extend'; }
      static get properties() {
        return {

        };
      }
      saludo() {
        console.log("Método elemento extendido..... Hola");
      }
      despedida() {
        console.log("Método elementos extendido.... Adios");
      }
    }
    window.customElements.define(TextExtend.is, TextExtend);
  </script>
</dom-module>

Que es un mixin

Como habíamos dicho al principio, los mixins nos permite compartir código entre los componentes.

Nos podemos encontrar componentes que sean parecidos y que puedan compartir código por funcionalidad, o también componentes que no tengan nada que ver entre ellos pero igualmente tengan ciertos comportamientos que se puedan repetir.

Un mixin lo podemos definir con una class factory (factoría de clases), y no es mas que una función que nos devuelve una clase.

Esta función recibe como parámetro una superclase y no devuelve una clase que extiende esa clase, incluyendo todos los métodos del mixin.

Ahora puedes ver el sentido a la breve introducción en este módulo sobre herencia en Javascript.

Como podemos usar un mixin

Lo llevamos haciendo durante casi 8 módulos con el resto de componentes y es usando un import.

<link rel="import" href="/path/loquesea.html">

Vamos aprovechar el mixin que usamos cuando importamos el event gesture en un módulo anterior.

<link rel="import" href="../../bower_componentes/polymer/lib/mixins/gesture-event-listener.html">

Una vez importado tendremos una nueva función que la llamaremos de la siguiente forma:

Polymer.GestureEventListeners(Polymer.Element)

Con la sentencia anterior tenemos la definición de un mixin.

Una función Polymer.GestureEventListener, que le pasamos una superclase  Polymer.Element, y esto nos devolverá todo el código de Polymer.Element mas el código del mixin.

class MiComponenteNuevo extends Polymer.GestureEventListeners(Polymer.Element) {}

En el ejemplo anterior hemos usado un mixin para los eventos de gestos, pero, ¿si nosotros tenemos otros mixins y necesitamos usarlos?.

Pues la solución es muy simple, ya que podemos encadenar varios mixins seguidos para usarlos todos.

Vamos hacer un ejemplo para entenderlo mejor:

class ComponenteMio extends Polymer.GestureEventListeners(CustomMixin1(CustonMixin2(Polymer.Element))) { }

Como el código anterior puede parecer raro vamos a explicar como internamente se ejecutaría:

  1. Le pasamos la clase Polymer.Element a el mixin CustomMixin2 y esto nos devuelve una clase
  2. Le pasamos la clase devuelta de CustonMixin2(Polymer.Element) CustomMixin1 y esta ejecución nos devuelve otra clase
  3. Ahora le aplicaremos a Polymer.GestureEventListeners la clase que nos devolvió el CustomMixin1

Como crear un mixin

Sabiendo que es un mixin y como usar un mixin, vamos hacer una parte importante, crear un mixin.

const mixinUtils = function(superclase) {
  return class extends superclase {
  
  }
}

Con el ejemplo anterior tenemos una plantilla para definir un mixin, pero que podemos añadir dentro del mixin.

Dentro del mixin podemos crear cualquier código Javascript que necesitemos:

  • Ciclo de vida.
  • Observers.
  • Propiedades.
  • Constructores.
  • Métodos que usemos para el mixin.

Como un mixin es una clase debemos invocar siempre a la superclase, por ejemplo ready() super.ready(), o en el constructor() con super().

A continuación algo de código muy simple para entender y comprobar como funcionan los mixins:

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>app-extend</title>
  <meta name="description" content="app-extend 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/app-extend-app/app-extend-app.html">
</head>

<body>
  <h1>EXTEND</h1>
  <app-extend-app></app-extend-app>
</body>

</html>

app-extend-app.html

<link rel="import" href="../../bower_components/polymer/polymer-element.html">
<link rel="import" href="../text-extend/text-extend.html">
<link rel="import" href="../utilidades/mixins.html">
<dom-module id="app-extend-app">
  <template>
    <style>
      :host {
        display: block;
      }
    </style>
    <button on-click="saludo">Saludo</button>
    <button on-click="despedida">Despedida</button>
    <button on-click="alertMixin">Alert Mixin</button>
    <div>Edad......[[edad]]</div>
    <button on-click="sumaEdad">Sumar edad</button>
    <button on-click="restaEdad">Resta edad</button>
    <button on-click="resetEdad">Reset edad</button>
  </template>

  <script>
    /**
     * @customElement
     * @polymer
     */
    // Extendemos del mixin y le pasamos la clase 
    // que se unira para devolvernos una clase con todo
    class AppExtendApp extends UtilidadesMixin(TextExtend) {
      static get is() { return 'app-extend-app'; }
      static get properties() {
        return {

        };
      }
      alertMixin() {
        // Método dentro del mixin
        this.alertMsn("Prueba");
      }
    }

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

text-extend.html

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

<dom-module id="text-extend">
  <script>
    class TextExtend extends Polymer.Element {
      static get is() { return 'text-extend'; }
      static get properties() {
        return {

        };
      }
      saludo() {
        console.log("Método elemento extendido..... Hola");
      }
      despedida() {
        console.log("Método elementos extendido.... Adios");
      }
    }
    window.customElements.define(TextExtend.is, TextExtend);
  </script>
</dom-module>

mixins.html

<script>
    const UtilidadesMixin = function (superClase) {
        return class extends superClase {
            alertMsn(msn) {
                alert(msn);
            }
            static get properties() {
                return {
                    edad: {
                        type: Number,
                        value: 0
                    }
                }
            }
            sumaEdad() {
                this.edad++;
            }
            restaEdad() {
                this.edad--;
            }
            resetEdad() {
                this.edad = 0;
            }
        }
    }
</script>

Si observamos el código veremos como extendemos ademas de los métodos también lo hacemos con las propiedades.

Par finalizar este módulo vamos aprender a preparar nuestro mixin para compartirlo.

Compartir nuestro mixin

Cuando desarrollamos nuestros mixins normalmente si es nuestro desarrollo propio y no lo van a usar otras personas, posiblemente sepamos donde esta cada parte del código y podamos evitar repetir nombres y código.

El problema aparece cuando nuestro mixin lo comienzan a usar otras personas, y que a su vez esas personas desarrollan nuevas funcionalidades que pueden coincidir en nombre, ya que un mixin al final es como tener una declaración global de todo nuestro Javascript.

Para nuestra suerte Polymer tiene una función que realiza un cacheo del mixin, evitando de esta forma que se aplique más de una vez.

La función es Polymer.dedupingMixin() y su funcionamiento es el siguiente:

  1. Memoriza la clase generada por el mixin, de esta forma no se tiene que volver a ejecutar la función.
  2. Si el mixin ya fue usado en una clase, evitar volver a usarlo.

Perfecto, ¿pero como uso esta función?. Pues igual que hacíamos para encadenar mixin, le pasaremos la declaración del mixin como parámetro.

const MixinCache = Polymer.dedupingMixin(function(superClase) {
  return class extends superClase {
    static get properties() {
      return {
        acceso: {
          type: Boolean,
          value: false
        }
      }
    }
  }
});

Otra de las soluciones que es una buena practica cuando queremos o necesitamos compartir nuestro código, es usar el patrón módulo.

El patrón módulo tiene una estructura sencilla, ya que es una función que trabaja como un contenedor.

Esto quiere decir que en su interior, se declaran una serie de variables y funciones que solo son visibles desde dentro del mismo.

Un pequeño ejemplo aplicando el patrón módulo:

window.MixinsGlobal = window.MixinsGlobal || {};

MixinsGlobal.PopupMixin = Polymer.dedupingMixin(superClase => { 
  return class extends superClase { 
     alertMsn(msn) {
      alert(msn);
     }
  } 
});