angular y polymer

Cómo combinar angular con polymer para reunir lo mejor de dos mundos

John Brack

archivado en: JavaScript / 1 marzo, 2016

Polymer me entusiasma y creo que los web components son formidables, el próximo episodio del desarrollo front; sin embargo, no tengo nada claro que se puedan manejar sin estar soportados por un framework js más clásico, como angular, ember o backbone. El planteamiento que hay debajo del polymer starter kit, una especie de boilerplate para trabajar con polymer, es interesante: como las matrioskas rusas que albergan una muñeca dentro de otra, los componentes se entrelazan entre sí para terminar formando un supercomponente que es la single page application final.

Sin embargo, este armazón no es suficiente, ni de lejos, para soportar una aplicación grande, con cientos de vistas, decenas de llamadas rest y no sé cuántos controladores y helpers varios. Ojo, esto no significa que no existan en polymer técnicas lazy load y demás estrategias de optimización que vayan dosificando cargas para que la aplicación no se sature; sino que la sustentación es frágil, complicada de trabajar en equipos grandes y de testar. Dicho de otra manera, una casa no se construye apilando una cosa encima de otra, sino que primero se arma una estructura sólida y luego se ponen las paredes, la luz, la fontanería y todos los demás componentes.

Al menos para mí, por lo tanto, polymer necesita algo más sólido por debajo, pero ¿necesitan estos frames holísticos, que en teoría te aportan todo lo que necesitas, apoyarse en polymer?

Lo primero que pensé cuando conocí polymer es que no tenía mucho sentido, pues en cierta manera las directivas ya están cumpliendo el papel de los componentes polymerescos. Pero estaba partiendo de un planteamiento equivocado, ya que se me había olvidado el gran problema de trabajar con angular y es que solo se lleva bien consigo mismo. Sí, tenemos una directiva formidable, un alarde ingenio y saber hacer, pero solo podremos reutilizarla en otro proyecto angular, puesto que estará sembrada de sintaxtis angularesca incompatible con nada que viva más allá de este framework. Sin embargo, un web component es -por definición- universal. No se casa con nadie. Es una pieza que deberíamos poder utilizar en cualquier contexto. Por lo tanto, al menos para mí, no solo se puede, sino que se debe combinar angular con polymer.

Comprendida la necesidad, veamos ahora cómo desarrollar la función (y sí, ya sé que hay módulos que hacen esto, pero me interesa que comprendamos lo que hay por debajo).

Desde polymer hacia el mundo exterior

Antes de ver el caso concreto de angular, creo que es interesante saber cómo puede comunicarse polymer con el mundo exterior, algo en teoría complicado por el propio concepto de encapsulación que rodea a los web components. Y digo en teoría porque por fortuna contamos con algo capaz de sortear cualquier closure y son los listeners.

Como es sabido, podemos enganchar eventos personalizados a cualquier elemento del dom. Por ejemplo, así  engancharíamos con document el evento «foo-changed» (como si fuera un click o cualquier otro normal).

document.addEventListener('foo-changed', function (e) {

console.log('foo ha cambiado');

}, true);

Y para lanzar ese evento es tan sencillo como usar el método dispatchEvent():

var myEvent = new CustomEvent("foo-changed");

document.body.dispatchEvent(myEvent);

Bueno, en realidad habría que añadir un polyfill para que funcionase también en la tostadora el explorer, pero vamos, que no es nada complicado.

(function () {

if ( typeof window.CustomEvent === "function" ) return false;

function CustomEvent ( event, params ) {

params = params || { bubbles: false, cancelable: false, detail: undefined };

var evt = document.createEvent( 'CustomEvent' );

evt.initCustomEvent( event, params.bubbles, params.cancelable, params.detail );

return evt;

}

CustomEvent.prototype = window.Event.prototype;

window.CustomEvent = CustomEvent;

})();

Por lo tanto, para comunicar un componente polymeresco con el mundo exterior basta con lanzar el customEvent que se está escuchando desde la página que lo alberga.

<dom-module id="host-component">

<template>

<button on-tap="setFoo">Cambiar foo</button>

</template>

<script>

Polymer({

is: 'host-component',

setFoo: function() {

var myEvent = new CustomEvent("foo-changed");

document.body.dispatchEvent(myEvent);

}

});

</script>

</dom-module>

Y es más sencillo aún si cuando definimos una propiedad le asignamos true al flag notify, pues de forma automática polymer lanza un evento hacia arriba formado por el nombre de la propiedad más el sufijo changed cada vez que cambia ^^.

Polymer({

is: 'host-component',

properties: {

foo: {

type: String,

value: "Haga Basin",

notify: true

}

},

setFoo: function() {

/* Al estar notify en true, de forma automática

se dispara el evento foo-changed */

this.foo = "hey hey";

}

});

Entendido esto, vamos ya sí con angular.

Desde angular hacia polymer

El camino que va desde fuera hacia dentro carece de obstáculos. Como a polymer le importa un pimiento desde dónde se cambia un atributo, el cual se liga a una propiedad, el binding de angular se refleja en los componentes de angular. Esto queda más claro en código (pongo el ejemplo más sencillo que puedo, para evitar ruido ambiental, por lo que me salto el controller as, el vulcanize y cualquier otra fórmula que desvíe la atención).

En el index, que es el receptáculo base de la single page, incluimos polymer y angular.

<!doctype html>

<html class="no-js" lang="es">

<head>

<!-- ... mandangas varias del head ... -->

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

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

<link rel="import" href="app/components/foo-component.html">

</head>

<body ng-app="bjLab">

<script src="/bower_components/angular/angular.min.js"></script>

<script src="/app/app.js"></script>

<script src="/app/main/main.js"></script>

</body>

</html>

Escribimos un controlador sencillo, que se limite a bindear una cadena en el scope.

main.js

angular.module('bjLab').controller('main', ['$scope', function($scope) {

$scope.bar = 'Bazinga!';

}]);

Y en la vista cargamos el componente y añadimos un botón para cambiar, desde angular, el valor de un atributo que se leerá desde polymer.

...

<div ng-controller='main'>

<foo-component dinamic-data="{{bar}}"></foo-component>

<p>

<button ng-click="bar='Haga Basin'">set dinamicData</button>

</p>

</div>

...

Preparamos el componente de polymer, en el cual definimos la propiedad que hemos incluido como atributo y a esta le añadimos un observer que se dispare cuando cambia el valor.

<dom-module id="foo-component">

<template>

dinamicData is {{dinamicData}}

</template>

<script>

Polymer({

is: 'foo-component',

properties: {

dinamicData: {

type: String,

observer: 'fooIsChanged'

}

},

fooIsChanged: function() {

console.log('foo is changed');

}

});

</script>

</dom-module>

¡Ale op! Si cambiamos el atributo desde angular, polymer lo recoge en el binding y el observer. Fantástico.

angular-polimer

Desde polymer hacia angular

El camino inverso es igual de sencillo si jugamos con los listeners. Para que se entienda, nos llevamos la función que cambia el valor al componente. Es decir, en la vista nos queda solo algo así:

<div ng-controller='main'>

<foo-component dinamic-data="{{bar}}"></foo-component>

</div>

Y en el componente añadimos la función y, lo que es más importante, declaramos el notify de la propiedad como true.

<dom-module id="foo-component">

<template>

dinamicData is {{dinamicData}}

<p>

<button on-tap="setDinamicData">set dinamicData</button>

</p>

</template>

<script>

Polymer({

is: 'foo-component',

properties: {

dinamicData: {

type: String,

notify: true

}

},

setDinamicData: function() {

this.set('dinamicData', 'Haga Basin');

}

});

</script>

</dom-module>

Ahora, sabiendo que el evento que se dispara de forma automática es el nombre de la propiedad con las mayúsculas convertidas en guiones y el añadido -changed, tan solo tenemos que añadir el listener en el controlador main.js.

angular.module('bjLab').controller('main', ['$scope', function($scope) {

$scope.bar = 'Bazinga!';

document.addEventListener('dinamic-data-changed', function (e) {

console.log('desde el controlador sé que foo ha cambiado');

}, true);

}]);

Bueno, en realidad, para estar seguros de que la escucha no se queda en el aire cuando cambiamos de controlador, habría que removerla en el on destroy del $scope.

function fooChanged(e) {

// hacemos algo y lanzamos un $setTimeout si queremos refrescar la vista

}

document.addEventListener('dinamic-data-changed', fooChanged, true);

$scope.$on("$destroy", function() {

document.removeEventListener('dinamic-data-changed', fooChanged, true);

}

);

Moraleja del cuento, es posible y recomendable combinar angular con polymer para obtener lo mejor de dos mundos: la solidez de un frame mvc con la reutilización de componentes universales.

Termino con un apunte sobre angular 2. El lector atento habrá observado que no he mencionado la segunda versión de este framework y es porque en ese caso sí que no veo nada claro cuáles serían las ventajas de combinar estas dos tecnologías. En esta versión, los controllers desaparecen y todo el peso recae en las directivas -un cambio sobre el que no me pronunciaré ahora- y, precisamente el chiste de este invento es sustituir las directivas de angular por componentes de polymer... de todas maneras, este es un tema sobre el que aún no tengo una opinión formada y que está sujeto a discusión.

|| Tags: , ,

valoración de los lectores sobre angular y polymer

  • estrellica valoración positiva
  • estrellica valoración positiva
  • estrellica valoración positiva
  • estrellica valoración positiva
  • estrellica valoración positiva
  • 4.8 sobre 5 (5 votos)

¿Te ha parecido útil o interesante esta entrada?
dormido, valoración 1 nadapensativo, valoración 2 un poco sonrisa, valoración 3 a medias guiño, valoración 4 bastante aplauso, valoración 5 mucho

Tú opinión es muy importante, gracias por compartirla!

4 respuestas a “angular y polymer

  1. Rubén Aguilera el dijo:

    Hola Marcos,

    Buen artículo. Solo decirte que precisamente la versión 2 de Angular trabaja con web components por lo que la integración con Polymer es mucho más natural. Simplemente haces uso de la etiqueta del componente en cualquier template de un componente de Angular 2. Limpio y muy sencillo, ya que, a mi juicio una de las fortalezas de la versión 2 es que se integra de forma natural con numerosas tecnologías como: Polymer, React, Meteor, etc….

    Saludos

  2. Arturo Batanero el dijo:

    Muy claro e informativo el artículo.

    La encapsulación es la pata del paradigma OOP que le faltaba a la tecnología Web.

    Polymer, Angular2, etc… todos hacen uso de esta encapsulación. Sin ella no me parece posible que el navegador sea una verdadera plataforma de aplicaciones, así que bienvenida sea.