Manual Polymer 2
Manual Polymer 2
Este es el manual de Polymer 2, la librería para el desarrollo con el estándar de los Web Components.
Polymer es una librería creada por Google para facilitar el trabajo en el desarrollo de componentes y
aplicaciones complejas.
Ahora que el estándar Web Components V1 es una realidad y el soporte en navegadores está prácticamente
generalizado, Polymer 2 se ha reformulado completamente para ofrecer una manera de desarrollar más ágil
y más cercana al estándar.
En Polymer 2 prima el desarrollo nativo, con Javascript estándar, lo que ofrece una mayor velocidad y
menor peso de las aplicaciones, ya que se apoya en todo lo que la plataforma web nos ofrece a los
desarrolladores. Tanto es así que la librería ocupa solo 11K en navegadores que no necesiten la carga de
polyfills.
En este Manual de Polymer 2 nos ponemos manos a la obra para acercar la mejor librería basada en Web
Components, capaz de extraer lo mejor del estándar y permitir una mayor velocidad y comodidad de
desarrollo. Explicaremos cómo se desarrollan componentes basados en Polymer 2 y cómo usar la
inacabable biblioteca de elementos ya listos para el desarrollo de aplicaciones complejas.
Las siguientes personas han participado como autores escribiendo artículos de este manual.
Comenzamos con una serie de artículos que nos aproximarán al desarrollo de componentes con la librería
de Google. Explicaremos qué es Polymer y en qué se basa para ofrecer al desarrollador quizás la mejor
plataforma para la creación de componentes y aplicaciones web.
Nuestro objetivo fundamental en estos primeros artículos es explicar fundamentos del estándar de los Web
Components V1, que permite el desarrollo de elementos personalizados con los cuales extender el HTML y
aportar nuevas funcionalidades a los navegadores, que podrán ser compartidas y reutilizables de manera
general. Luego veremos cómo el desarrollo con Web Components y el desarrollo con Polymer 2 pueden ser
muy similares, ya que la librería de Google se basa directamente en el estándar. Por último conoceremos
cómo es un componente de Polymer, sus diferentes partes y cómo usar el toolbox disponible en Polymer 2
para facilitarnos el día a día en el desarrollo de aplicaciones web.
En [Link] nos hemos subido al tren de los Web Components porque creemos que el
desarrollo basado en estándares abiertos es la mejor garantía para el futuro. Desde hace casi dos años
venimos ofreciendo manuales para lanzarse a este mundo y en ese sentido ya hemos publicado un Manual
de Web Components y el Manual de Polymer. Sin embargo, en esta ocasión tenemos que empezar de nuevo
porque la tecnología ha tenido importantes mejoras que nos obligan a actualizarnos.
Para introducirnos en esta ocasión en el mundo de los Web Components vamos a usar la librería Polymer 2,
cuyo lanzamiento ha sido realizado hace poco. Es una tecnología que aprovecha todas las ventajas de los
Web Components y nos hace más fácil el desarrollo de componentes web encapsulados y reutilizables.
En este artículo pretendemos ofrecer una introducción a Polymer 2 y los Web Components V1, para ir
abordando una materia que sin duda está revolucionando el desarrollo frontend. En los próximos artículos
nos pondremos manos a la obra, ya con código, con el que poder experimentar todas estas novedades.
Web Components V1
Los Web Components son un estándar abierto que ha sido impulsado principalmente por Google y su
navegador Chrome. Sin embargo, su adopción en el total del abanico de navegadores ha sido más lenta de
lo esperado inicialmente, ya que involucra a muchas empresas que tienen maneras diferentes de entender el
desarrollo para la web.
La primera especificación de Web Components se nombró como V0 (Versión cero) y resultó un gran
avance, pero no llegó a estar incorporada de manera generalizada. Es la que en su día explicamos en el
Manual de Web Components. Básicamente el concepto sigue siendo el mismo, por lo que si no sabes qué
son los componentes es interesante la lectura al menos de los primeros artículos en los que se repasan los
conceptos principales y las especificaciones relacionadas con la tecnología.
Nota: Por si alguien no lo sabe todavía y para resumir rápidamente de manera simplificada, los Web
Components nos permiten crear nuevos elementos, como etiquetas HTML, que extienden el
comportamiento de los navegadores. Podemos tener elementos para hacer cualquier cosa, como
interfaces de usuario no disponibles en el HTML estándar o componentes que ofrecen funcionalidad de
una aplicación completa. Los componentes tienen la gracia de poder reutilizarse y compartirse
fácilmente, con lo que los desarrolladores pueden extender la web y llevarla a los límites de la
imaginación de cada uno. Web Components nos ofrece una manera estándar de extender las
funcionalidades de los navegadores, de manera que un desarrollo de una persona puede servir para toda
la comunidad y ser compatible en cualquier navegador, de escritorio o móvil. Para quien venga del
desarrollo con otros frameworks y librerías, como React, Angular, VueJS, etc., Web Components hace
de manera estándar las cosas que todos estos frameworks realizan por cuenta propia, con
implementaciones propietarias, creadas por ellos mismos.
Después del empuje inicial de Google a esta tecnología y con los acercamientos y sugerencias del resto de
fabricantes de navegadores, se ha llegado a una nueva versión del estándar que cubre las necesidades y
expectativas de todos. Hoy podemos decir que Web Components V1 ha sido aceptado por todos los
fabricantes de navegadores, lo que ya mismo es una realidad, no solo en Google Chrome, sino en Safari, y
pronto en Firefox, Edge, etc.
Web Components V1 en el momento de escribir este artículo está totalmente implementado, y de manera
estable, en Chrome y Opera. El navegador Safari dispone de las partes más críticas de Web Components ya
implementadas y Firefox junto con Edge van un poco más retrasados pero han dado el visto bueno también
a la tecnología. De todos modos, independientemente del soporte de los navegadores la tecnología es
perfectamente usable en este momento, pues donde todavía no llega el soporte nativo existen los "Polyfills"
(Librerías de Javascript para implementar soporte a navegadores todavía no compatibles con una tecnología)
que nos permiten ampliar la compatibilidad a todos navegadores del mercado. En la siguiente imagen
Nota: Esta imagen actualizada la puedes ver en la "Home" de la tecnología Web Components. Allí
encontrarás mucha información del estándar y un catálogo de componentes que puedes usar ya mismo
en cualquier proyecto.
"UseThePlatform" se ha usado como el slogan de Polymer y los Web Components. Nos trata de indicar que
usemos en la medida de lo posible lo que el navegador nos ofrece. Todo aquello que el navegador ya
dispone en sus APIs de Javascript es más ligero de ejecutar que cualquier librería que pongamos por encima
para hacer las mismas cosas.
Tenemos que pensar que la web fue ideada para el trabajo con documentos, donde el texto y las imágenes
eran los protagonistas. Sin embargo, con el tiempo la web fue adaptándose a las necesidades de los usuarios
y empresas, volviéndose una plataforma de ejecución de aplicaciones. Frente a esa necesidad nacieron los
frameworks y librerías, que nos ofrecían una base de código útil para realizar todas aquellas cosas que los
estándares no aportaban en el pasado.
En los últimos años desarrollo basado en componentes se ha consolidado como la mejor de las alternativas
y prueba de ello es la adopción del paradigma por todas las librerías y frameworks más usados. Éstos
crearon sus propios mecanismos para que los desarrolladores fueran capaces de construir elementos
encapsulados, con los que resolver las necesidades más diversas. Las aplicaciones más complejas se pueden
implementar en base a componentes, donde unos elementos que se apoyan en otros. El problema es que
toda la base de código que nos aporta un framework tiene un coste elevado, pues ralentiza no solo el tiempo
de carga de las aplicaciones web, sino que además implica mucho esfuerzo en tiempo de procesamiento, ya
que deben procesar mucha cantidad de código al interpretar aplicaciones complejas.
Web Components ha conseguido crear unos estándares abiertos para la construcción de los elementos
personalizados, con código nativo que el navegador puede ejecutar sin esfuerzo. Usar los estándares abiertos
disponibles para la web, aparte de ofrecer un mejor rendimiento, nos asegura un ciclo de vida de las
aplicaciones más dilatado en el tiempo. Cualquier navegador o cliente web en general, presente o futuro, que
soporte los estándares será capaz de ejecutar las aplicaciones creadas en base Web Components.
Paralelamente, cada vez que los fabricantes de navegadores sean capaces de mejorar la plataforma, las
aplicaciones creadas en base a estándares se verán más directamente beneficiadas.
Polymer es una librería basada en Web Components, por lo que consigue exprimir todos los
comportamientos nativos que nos ofrecen los navegadores para hacer las cosas, de manera más ligera que
otras librerías o frameworks.
Como hemos dicho, donde no llegan los navegadores existe la posibilidad de cargar Polyfills que ofrecen un
soporte no nativo, tanto a Web Components como a muchas otras tecnologías HTML5. Gracias a los
polyfill conseguimos algo muy importante: cualquier web que use Web Components será compatible en
todos los browsers.
Los Polyfills son por tanto un actor importante en la actualidad, ya que el soporte nativo está siendo
desarrollado por algunos fabricantes todavía. Pero lo cierto es que no son la mejor solución, ya que recargan
el navegador con código Javascript y por tanto lo hacen más lento al procesar páginas basadas en Web
Components.
Al contar con soporte nativo, los navegadores son más rápidos y lo que es tan importante, las webs son más
ligeras de carga, ya que la mayoría del Javascript no se tiene que descargar.
Hoy, las especificaciones más críticas de Web Components están completamente implementadas en
Chrome, Safari y Opera, por lo que no solo ordenadores de escritorio, sino la mayoría de los móviles, son
compatibles de manera nativa con Web Components, lo que resulta un excelente motivo para confiar en
esta tecnología.
Nota: Al tener soporte nativo Web Components lo hace tan rápido como el mismo Javascript "Vanilla",
ya que Web Components es simplemente Javascript.
Sin embargo, para un navegador que dependa de Polyfills, usar Web Components no representaría una
ventaja particular. Dicho de otra forma, una aplicación Angular, React, VueJS, etc., sería similar, a usar Web
Components con Polyfills, pues todos dependerían de Javascript no nativo para implementar cosas
parecidas. Por este motivo, todos los mejores frameworks han anunciado que en un futuro cambiarán su
manera de trabajar, para basarse en el estándar Web Components y apoyarse directamente en el Javascript
nativo, algo que hoy ya tenemos con Polymer 2.
Polymer 2.0 es capaz de ir un sensiblemente más lejos de su predecesor, aunque la mayoría de las novedades
más significativas se derivan directamente de las propias novedades del estándar de los Web Components y
su creciente soporte nativo en los navegadores.
Hay una charla del Google IO 2017 muy interesante sobre las novedades de Polymer 2.0 y los Web
Components en general, en la que ofrecen un dato que me parece importante mencionar sobre los avances
de Polymer 2.0, con respecto al peso en Bytes y la separación del código de los polyfill: "Future, Faster:
Unlock the Power of Web Components with Polymer".
En ella podremos encontrar esta diapositiva, en la que vemos el peso de Polymer junto con los Polyfill en
cada familia de navegadores en la actualidad.
Como se puede ver, en Chrome, que ofrece soporte completamente nativo, el peso de Polymer 2.0 más los
polyfill es de 11KB, en Safari actualmente subiríamos a 14KB, Firefox y Edge 33KB y los antiguos Internet
Explorer 37KB.
Ante esta reducida cantidad de código queda todavía más clara la idoneidad de usar Polymer 2.0 junto con
otros frameworks como podrían ser Angular o React, además de ser posible la interoperabilidad por defecto
de manera nativa por medio del estándar Web Components.
Nota: Hay un matiz que es justo mencionar y es que Polymer 2 no es solo el propio núcleo, sino que
también hay muchas otras cosas que se ofrecen por medio de una biblioteca de componentes ya listos
para usar en las aplicaciones. Cuando decimos que Polymer 2.0 pesa 11KB no se están incluyendo
muchos elementos que sin duda acabarás usando en las aplicaciones, como el sistema de routing, el
sistema de almacenamiento local, los elementos para construir layouts avanzados, elementos de interfaz
Conclusión
Éstas y muchas otras novedades permiten ilusionarse con la nueva versión de la librería Polymer para
desarrollo basado en el estándar abierto de los Web Components. Iremos abordando en el futuro, con
nuevos artículos muchas de las novedades interesantes, no solo de la propia librería, sino también del
universo de los custom elements que tenemos a nuestra disposición hoy para el desarrollo basado en
Polymer.
Estamos seguros que estarás ansioso con ver algo de código, así que en la siguiente entrega nos pondremos
manos a la obra con algo más práctico, que nos permita ver cómo construir fácilmente componentes en
Polymer 2.
Puedes ver el siguiente vídeo, con un resumen de las ventajas de Polymer y Web Components V1, además
de un adelanto del próximo artículo de este manual.
En el pasado artículo hemos ofrecido una introducción teórica a los objetivos de Web Components y
Polymer 2, pero no estará completa sin algo de código para que podamos experimentar lo sencillo que
puede llegar a ser crear nuestro primer componente.
El objetivo en esta ocasión es ofrecer un ejemplo de elemento personalizado creado con Web Components
y enfrentar este código al que necesitaríamos para hacer ese mismo componente en Polymer 2, apreciando
las similitudes y también las diferencias. Por tanto, este artículo nos servirá como introducción práctica de
tanto Web Components V1 como de Polymer 2.
Pero antes de comenzar cabe aclarar qué significa "Custom Element", la principal de las especificaciones de
Web Components. Básicamente un elemento personalizado es un nuevo elemento de HTML creado por
nosotros mismos para resolver cualquier tipo de necesidad que no esté implementada en el HTML estándar.
Así pues, cuando mencionamos "componente" en general, casi siempre estamos refiriéndonos a un "custom
element". Los componentes pueden ser sencillos o complejos. Los complejos a menudo se apoyan en
componentes más sencillos para resolver las tareas. Una aplicación generalmente será un componente que
contiene a su vez un árbol de componentes que se relacionan entre sí y trabajan para resolver un conjunto
de necesidades de un negocio.
Debes saber que, el marco de la tecnología "Web Components", custom element es solo una de sus
especificaciones y, por lo general, para implementar componentes nos tenemos que apoyar en otras de las
especificaciones, como "Shadow Dom" o "Templates". Puedes saber más sobre generalidades de las
especificaciones en el artículo Qué son Web Components. [Link]
[Link]
Para definir un elemento personalizado se debe utilizar una clase, de programación orientada a objetos. Nos
referimos lógicamente a clases Javascript ES6 como las que explicamos en el manual de la versión 2015 del
estándar ECMAScript (ES6). Posteriormente tenemos que asociar tal clase como un nuevo elemento dentro
del navegador. Ambos pasos los veremos representados en el siguiente código.
constructor() {
super();
[Link]('hola-componentes', HolaComponentes);
En el primer pedazo de este código se define una clase llamada HolaComponentes. Como manda el
estándar V1, esta clase debe heredar de un elemento HTML. En este caso usamos "HTMLElement", que es
la clase que define un elemento HTML predeterminado. La herencia, como debes saber, se indica mediante
la declaración extends, de la que tienes más información en el artículo de Herencia en las clases de Javascript
ES6.
Como código de la clase tenemos básicamente un constructor, que crea un poco de HTML para usarlo
como contenido de nuestro componente. Ese código del constructor usa métodos que pertenecen al API de
Web Components V1. Por tanto, se trata de Javascript estándar, como attachShadow(), método que sirve
para añadir "Shadow Dom" al componente, que básicamente crea un nuevo nodo hijo, dentro del elemento
personalizado, del que pueden a su vez colgar un árbol de etiquetas. El "shadow DOM" tiene la
característica de encapsular el árbol de etiquetas dependientes de un elemento y {mode: 'open'} hace
referencia al nivel de encapsulación. A ese nuevo nodo Shadow DOM le estamos agregando simplemente
un párrafo, gracias a manipular directamente su propiedad innerHTML.
Nota: Si a pesar de estas explicaciones este código te parece muy extraño quizás te ayudaría saber algo
más sobre las clases ES6 y sobre la especificación de Shadow DOM.
Una vez definido el nuevo elemento ya lo puedes usar. Para ello simplemente tienes que colocar en el
cuerpo de la página la etiqueta HTML que se ha definido en la creación del componente.
<hola-componentes></hola-componentes>
Nota: todos los custom elements necesitan en su nombre un caracter "-" al menos, para distinguirlos de
etiquetas estándar HTML existentes o futuras, que no llevan nunca un guión.
Para que quede perfectamente claro, dejamos el listado de este ejercicio en una página HTML completa.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<hola-componentes></hola-componentes>
<script>
constructor() {
super();
[Link]('hola-componentes', HolaComponentes);
</script>
</body>
</html>
Fíjate que el código anterior es HTML estándar y Javascript nativo, que soportan los navegadores
compatibles con Web Components V1. Para el resto de los navegadores solo funcionaría si se carga el
correspondiente polyfill, cosa que no hemos hecho todavía. En el momento de escribir este artículo, si lo
abres en Chrome, Opera o Safari funcionará. No necesitas cargar ninguna librería extra gracias a su soporte
nativo!
Polymer 2 se apoya al máximo en lo que el propio navegador dispone, por lo que no hay mucha diferencia
entre crear un componente nativo o un componente creado con Polymer 2. Pero además te ofrece algunas
nuevas características, como mayor facilidad para crear Shadow Dom, utilidades para mantener el estado del
componente, para gestionar eventos personalizados y sincronizar los valores de las propiedades entre fuera
y dentro del componente, etc. En adelante iremos aprendiendo todas estas ventajas, ahora nos centraremos
en un código suficientemente simple para comenzar sin demasiada dificultad.
Si queremos usar Polymer 2 lo primero que tendremos que hacer es importar la librería, lo que se consigue
con un HTML Import.
Nota: HTML Import es otra de las especificaciones de Web Components que sirve para importar,
adquirir, incorporar código que está en otros documentos HTML.
Por facilitar la labor vamos a usar el CDN de Polymer 2, aunque más adelante veremos que lo común es
usar gestores de dependencias como Bower para traerse el código de Polymer, o de otros elementos en los
que queramos apoyarnos para hacer una aplicación.
Una vez realizado el import el navegador ya conoce Polymer, por lo que a continuación podemos crear
nuestra clase ES6 para implementar el componente. Por último tendremos que hacer el correspondiente
[Link]() para registrar el elemento en el navegador, igual que antes.
El próximo código observarás que Polymer 2 puede ser muy similar en su modo de trabajo a cómo se creó
el componente anteriormente, usando el estándar Web Components V1, sin apoyarse en ninguna librería.
[Link]([Link], HolaComponentes);
Luego el [Link]() es prácticamente igual. Solo que nos apoyamos en la propiedad "is" de la
clase, para acceder al nombre del custom element que se está definiendo.
Para que esto funcione, en el cuerpo de la página debería figurar el template al que se está accediendo en
"static get template()", que podría tener una forma como esta:
<template id="mi-template">
</template>
Nota: Si quieres saber más de templates te recomendamos la lectura del artículo sobre la etiqueta
template de Web Components.
Para acabar, y a fin de no dejar dudas, colocamos el código completo de una página en la que se usa este
componente creado con Polymer 2.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<template id="mi-template">
</template>
<script>
[Link]([Link], HolaComponentes);>
</script>
<hola-componentes></hola-componentes>
</body>
</html>
Nota: Quizás pueda haber decepcionado un poco la cantidad de código necesaria para crear un
elemento de Polymer, en relación a la cantidad de código usando el estándar Web Componentes V1,
pues es incluso algo mayor. Para un componente tan simple como este no hay mucha ganancia entre
usar Polymer o limitarnos al Javascript nativo. No te preocupes, más adelante, cuando las cosas se
pongan un poco más interesantes, observaremos todas las ventajas de apoyarse en Polymer.
Con esto hemos podido realizar nuestro primer acercamiento con código al estándar de Web Components
V1 y a Polymer 2, para crear un primer custom element mediante dos alternativas de código sencillo:
Javascript nativo Vs Polymer.
Solo cabe aclarar que las cosas no se hacen exactamente en Polymer 2 como hemos visto en este artículo,
pues existen algunos otros detalles que todavía no conoces y que debemos de explicar para producir un
código más ajustado a la práctica habitual. En el siguiente artículo podrás profundizar en nuevos conceptos,
abordando la estructura habitual de desarrollo de componentes en Polymer 2.
En noviembre de 2017 impartimos unas clases dedicadas a Web Components y Polymer 2, dentro del
marco del Curso de Polymer 2 de EscuelaIT. En este vídeo que encontrarás a continuación tenemos la
primera de las clases, en la que explicamos muchas cosas altamente relevantes para seguir ahondando en lo
explicado en el presente artículo.
En esta clase comenzamos con una introducción a los Web Components V1 de modo general, explicando
cómo es el desarrollo de componentes usando solamente Javascript. Luego estudiaremos cómo crear esos
mismos componentes mediante Polymer 2.
En el artículo anterior del Manual de Polymer 2 explicamos cómo crear un primer componente,
centrándonos en dos alternativas de desarrollo: 1) el estándar Web Components V1 y 2) Polymer 2.
Pudimos ver que existen muchas similitudes, ya que la mayoría de lo que se hace en Polymer 2 está definido
por el propio estándar ofrecido por el Javascript nativo.
Sin embargo, ya advertimos que habíamos alterado algo la manera con la que se acostumbra a trabajar en
Polymer 2, con la intención que las diferencias de sintaxis fueran mínimas, con respecto a la alternativa de
desarrollo de custom elements con Javascript nativo.
En este artículo queremos explicar entonces cuál es el modo habitual de desarrollo de un componente de
Polymer, reconociendo sus principales partes. No dista mucho de lo que hemos visto ya, así que seguro que
te resultará sencillo, e incluso más claro y compacto. Además, a las personas que vengan de Polymer 1.x
seguro que les resultará muy familiar.
Etiqueta dom-module
En un elemento de Polymer trabajamos con una etiqueta llamada dom-module, en la cual se engloban las
distintas partes que componen un componente.
Como puedes imaginar, dom-module es un componente en sí, que nos sirve de base para desarrollar
elementos de Polymer 2. En esta etiqueta debemos comenzar indicando como atributo el nombre del nuevo
elemento que se está implementando.
<dom-module id="nombre-componente">
</dom-module>
Entre dom-module y su cierre colocaremos todo el código del componente. Además, como puedes apreciar,
el nombre del componente que se está creando, indicado en el atributo "id", debe de tener al menos un
carácter guión "-".
Es la práctica habitual que el componente se defina en un archivo independiente, que tendrá el mismo
En ese archivo además colocaremos los import HTML de las dependencias del componente, que al menos
será la propia librería Polymer. Es decir, en el archivo HTML de implementación del componente
tendremos algo como esto:
<dom-module id="nombre-componente">
</dom-module>
Dentro de un componente de Polymer podemos colocar un template con el código HTML que
necesitaremos para la representación de ese elemento en la página. Las etiquetas HTML que coloquemos en
el template formarán parte del shadow dom del componente que estamos implementando.
<template>
<h2>Esto es un test</h2>
</template>
Nota: La etiqueta template ya la soportan todos los navegadores y simplemente es como un HTML que
no aparecerá en el cuerpo de la página, hasta que se carge mediante algún tipo de control con Javascript.
Puedes saber más sobre la etiqueta template.
Nosotros como desarrolladores no necesitamos hacer nada para que ese template aparezca en la página al
ser usado el componente. Es decir, el trabajo de volcar ese template en el shadow dom del componente
corre por cuenta de Polymer.
Recuerda que esta etiqueta template la colocarás dentro de dom-module, así que te quedará algo como esto:
<dom-module id="nombre-componente">
<template>
<h2>Esto es un test</h2>
</template>
</dom-module>
El componente que estamos generando tiene la posibilidad de tener sus propias reglas de estilos, que no
afectarán más que al HTML que tengamos dentro del template.
El CSS para los estilos lo colocamos dentro de la etiqueta template, como sigue a continuación:
<template>
<style>
h2 {
background-color: orange;
color: white;
</style>
<h2>Esto es un test</h2>
</template>
Como decimos, gracias a la encapsulación de los componentes, son autónomos del resto de la página. Si en
la página donde se use este componente hay un estilo CSS definido para los H2, no entrará en conflicto con
el estilo de nuestro elemento recién creado. Tampoco los estilos definidos en nuestro componente afectarán
al HTML de la página donde se use, o a otros web components que pudiera haber.
Ahora nos queda la parte del Javascript, que también debemos colocar dentro de dom-module para que
todo funcione como debería ser. Esa es la parte que ya vimos en el anterior artículo, así que algo ya te debe
de sonar.
return 'nombre-componente';
constructor() {
super();
Fíjate que debemos nombrar con un getter el componente, en la propiedad "is". El constructor en realidad
no hace nada, por lo que no importa si lo ponemos o no.
Muy importante, debemos colocar luego el correspondiente define(), para que el navegador use esta clase
recién escrita como implementación del nuevo elemento.
[Link]([Link], NombreComponente);
Ahora que ya lo tenemos todo, veamos el código completo que hemos generado.
<dom-module id="nombre-componente">
<template>
<style>
h2 {
background-color: orange;
color: white;
</style>
<h2>Esto es un test</h2>
</template>
<script>
return 'nombre-componente';
[Link]([Link], NombreComponente);
</script>
</dom-module>
Este código lo colocaremos, como se ha dicho, en un archivo aparte, que generalmente llamaremos con el
mismo nombre del componente, en este caso "[Link]".
Recuerda que, para poder usarlo desde otra página web, debes realizar el correspondiente import en la
página donde deseas que este componente se conozca.
Y luego por supuesto colocar la etiqueta del componente, y su cierre, en el lugar donde quieres que
aparezca.
<nombre-componente></nombre-componente>
Ahora vamos a ver el código de otro componente simple. Es prácticamente igual que el anterior, aunque
tiene un DOM un poquito más extenso y unos estilos un poco más elaborados.
<dom-module id="navegador-secciones">
<template>
<style>
nav {
background-color: #eef;
padding: 10px;
ul {
margin: 0;
padding: 0;
list-style-type: none;
li {
display: inline-block;
margin-left: 15px;
</style>
<nav>
<ul>
<li>
<a href="[Link]">
Enlace 1
</a>
</li>
<li>
<a href="[Link]">
Enlace 2
</a>
</li>
<li>
<a href="[Link]">
Enlace 3
</a>
</li>
</ul>
</nav>
</template>
<script>
[Link]([Link], NavegadorSecciones);
</script>
</dom-module>
Te puede llamar la atención que no estamos usando ninguna clase (class de CSS) en el template ni en los
estilos, algo que sería muy necesario en el HTML/CSS tradicional para poder definir el aspecto de
elementos específicos, sin que afecten al resto. Esto es gracias a la encapsulación, ya que los estilos no salen
para fuera del componente.
Por si no quedase claro, dejamos el listado de un archivo donde hacemos uso de los dos componentes
creados en este artículo:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<style>
</style>
</head>
<body>
<nombre-componente></nombre-componente>
<navegador-secciones></navegador-secciones>
</body>
</html>
Ahora puedes complementar las explicaciones de este artículo mediante el siguiente videotutorial en el que
repasamos la estructura de un componente desarrollado con Polymer 2.
En el siguiente artículo abordaremos otro de los puntos importantes para completar nuestros primeros
pasos con la liberería Polymer, explicando algunas de sus principales herramientas, que componen el
toolbox de Polymer 2.
Instalar Polymer 2
Cómo instalar Polymer 2. Comenzamos a explicar flujos de trabajo habituales con Polymer 2 y las
herramientas disponibles en el Polymer CLI. Estructura de una aplicación básica.
En los pasados artículos del Manual de Polymer 2 hemos comenzado a usar la librería para el desarrollo de
componentes muy sencillos, lo que seguramente nos han dado una idea sobre lo fácil que es extender la web
con nuevos elementos que resuelvan necesidades de las aplicaciones. Sin embargo, nos hemos saltado toda
la parte dedicada al flujo de trabajo habitual al trabajar con Polymer, que será necesario conocer para sacarle
partido a las herramientas (toolbox) que se han creado para mejorar la productividad y las aplicaciones
desarrolladas.
Así pues, vamos a ponernos manos a la obra para dedicar un poco de tiempo a conocer algunas
herramientas y utilidades, así como pasos necesarios para instalar Polymer 2 en local y comenzar a
desarrollar más profesionalmente.
El primer paso para poner nuestros ordenadores a punto es instalar Git. Git, por si no lo sabes, es un
sistema de control de versiones muy popular, usado en la industria en la mayoría de las empresas. Ya hemos
tenido ocasión de explicar esta herramienta en el Manual de Git, por lo que no vamos a abordar todo desde
cero, sino centrarnos en por qué es importante Git en Polymer.
Polymer no te obliga a trabajar con un sistema de control de versiones, o con Git en particular, pero sí es
necesario para trabajar con Bower, del que hablaremos de aquí a poco. No obstante, algunas de las
herramientas de Polymer 2 sí usan Git, por ejemplo los templates para comenzar aplicaciones sobre una
base de código.
Por tanto, debes comenzar por hacerte con Git, en la página [Link] Encontrarás versiones
para tu sistema operativo, ya sea Windows, Linux o Mac Os X.
Bower es el sistema más utilizado en Polymer para el mantenimiento de las dependencias. Básicamente
Bower sirve para mantener dependencias en proyectos frontend en general, y aunque actualmente no es el
más completo ni el más recomendable, sí es el más simple.
Por si no lo sabes, un gestor de dependencias es un software que sirve para instalar y mantener actualizadas
todas las piezas de software que usas para desarrollar un proyecto. Cuando creas una aplicación
generalmente no lo haces desde cero y te apoyas en una serie de librerías, plugins, componentes, etc.
generalmente de código abierto. Todo eso son las dependencias y Bower te sirve para mantenerlas al día.
Como vamos a necesitar Bower, te recomendamos instalarlo y conocer sus comandos básicos. Como este
tema ya lo hemos tratado previamente, te recomendamos el artículo de Bower para obtener más
información.
Polymer CLI
Ahora sí, vamos a conocer las herramientas específicas de Polymer 2 y debemos comenzar por el Polymer
CLI. CLI son las siglas de Cliente por línea de comandos y básicamente es una herramienta heredada de
Polymer 1, ya revisada en el pasado en el artículo sobre Polymer CLI,
[Link] que funciona de manera similar en la versión 2.
Para instalar el CLI de Polymer tienes que disponer de NodeJS instalado en tu máquina. Por lo que deberías
entrar en la página de Node [Link] y descargar el instalador.
Nota: NodeJS también es necesario para poder instalar Bower, así que asumimos que ya sabes algo de
esta plataforma. Instalarla es tan fácil como usar el instalador o si estás en Linux hacerlo con sus
repositorios. No obstante, tenemos el Manual de Node para más información.
A partir de entonces dispondrás de una serie de comandos nuevos relacionados con Polymer, para crear
aplicaciones, para servir páginas y aplicaciones, llevarlas a producción, para crear proyectos de
componentes, etc. Todos los comandos del CLI comienzan con la palabra "polymer" seguida de la acción
que quieras realizar.
Una de las primeras acciones que debes aprender es a inicializar proyectos. Existen diversos tipos de
proyectos que puedes crear con el CLI de Polymer, aunque de momento vamos a crear uno vacío.
El comando para comenzar es "polymer init", aunque te recomendamos hacerlo desde una carpeta vacía.
Debes abrir una ventana del terminal y luego crear la carpeta en el directorio donde coloques tus proyectos.
mkdir test-cli
cd test-cli
polymer init
Verás que te ofrece una serie de alternativas, de momento podemos crear una aplicación vacía con la opción
"polymer-2-application". Puedes seleccionarla con los cursores y luego apretar "enter".
El nombre de la aplicación
El elemento principal, raíz de todos los componentes que formarán parte de tu aplicación
Una breve descripción de la aplicación
Nota: Estas solicitudes de información de momento no son demasiado importantes, pues estamos en
un simple test. Podemos poner cualquier cosa. Además ofrece unos valores por defecto que colocará si
no introducimos otros. El componente raíz, por si no lo sabes, es el elemento que mantendrá el código
de toda tu aplicación, que usualmente se apoyará en un árbol de componentes para realizar todas las
tareas que sean necesarias.
Con esto Polymer CLI se encarga de crearnos una estructura de proyecto simple y descargar la lista de
dependencias inicial. Las carpetas y archivos serán parecidos a estos:
Nota: no te asustes con la cantidad de carpetas que crea, o las dependencias situadas en la carpeta
"bower_components". Realmente de momento no necesitamos tantas cosas, pero lo cierto es que hay
mucho trabajo ya listo para facilitarnos la vida, al que le sacarás provecho en su debido momento.
Servir un proyecto
Polymer saca gran partido de las últimas características de la plataforma web, y muchas de ellas requieren
niveles de seguridad determinados, que solo están disponibles cuando se accede a las páginas por medio de
un servidor.
Es por ello que, en muchos casos, para ejecutar un proyecto realizado con Polymer no podemos
simplemente dar un doble clic a un archivo ".html" para abrirlo en el navegador. En cambio, debemos
abrirlo a través de un servidor web.
Debido a la necesidad de un servidor web, el propio CLI incorpora uno, que podemos lanzar con el
comando:
polymer serve
Esto arranca un servidor web y nos muestra la dirección a la que tenemos que acceder en el navegador para
abrir nuestro proyecto. Esta es la salida (o una parecida) que veremos al ejecutar el comando desde la
carpeta de nuestra aplicación vacía de Polymer:
Ahora ya solo queda acceder a esa URL en el navegador para ver la aplicación funcionando.
El CLI de Polymer también hace tareas importantes para cuando llevamos a producción un proyecto, entre
otras la compilación y compactación del código de componentes y aplicaciones. Dado que Web
Components V1 usa la versión de Javascript ES6, es importante que se ofrezca una versión transpilada a
ES5 para los navegadores que no soportan ES6.
En el proceso de "build" (comando "polymer build" del CLI) se hacen estas tareas. También, el comando
"polymer serve" se encarga la traducción a ES5 en tiempo de ejecución, si el navegador cliente lo require.
De momento no vamos a entrar en más detalles porque hay temas más urgentes de conocer antes de llevar a
producción aplicaciones desarrolladas con Polymer 2.
Ahora voy a describir brevemente la estructura del proyecto, no ítem por ítem, sino simplemente lo que
necesitamos reconocer para empezar a trabajar.
Carpeta "bower_components": aquí se guardan las dependencias instaladas por Bower. Verás el
código de Polymer, el código de los Polyfills que algunos navegadores necesitan y algunos
componentes básicos que se pre-instalan con tu aplicación.
Carpeta "src": Aquí están las fuentes de código de los componentes de tu aplicación. De
momento solo hay un componente, que es el componente raíz.
[Link]: este es el archivo inicial, que arrancará tu aplicación. En el código del index encuentras
cosas muy importantes, básicas para que tu aplicación se arranque y funcione correctamente.
Nota: Luego veremos otros tipos de proyectos que también creamos mediante el Polymer CLI y que
disponen de estructuras de más complejas, con mucho más código creado de antemano y capaces de
satisfacer otras necesidades y casos de uso.
En este momento queremos que te fijes en unos pedazos de código importantes como estos:
1.- La inclusión del script "[Link]", que es el archivo que se encarga de cargar todos los
polyfills necesarios, para que el navegador donde se esté ejecutando la aplicación sea compatible con Web
Components V1.
<script src="/bower_components/webcomponentsjs/[Link]"></script>
2.- El import del componente raíz de la aplicación, que está en la mencionada ruta "src".
Nota: repara que el componente raíz en tu caso puede tener nombre distinto, pues depende de lo que
indicaste en las preguntas derivadas de la ejecución del comando "polymer init".
3.- El uso del componente raíz, que es el único contenido que se encuentra en el BODY de la página.
<test-cli-app></test-cli-app>
Ese componente es el padre de todo el árbol de componentes que componen una aplicación compleja en
Polymer.
Como hemos prometido, queremos avanzar un poco más para ver cómo sería el modo de desarrollo de una
aplicación Polymer con las herramientas que ya conocemos. Para ello vamos a modificar un poco el
componente raíz, colocando alguna cosa adicional. Mi objetivo ahora es mostrar cómo somos capaces de
instalar nuevos componentes entre las dependencias del proyecto y cómo se usarían.
Vamos a usar un nuevo componente para crear campos "input" con el aspecto de "Material Design". Ese
componente ya está creado y forma parte de la biblioteca de elementos de Polymer. Puedes conocerlo en la
página [Link]
Lo instalamos mediante el gestor de dependencias Bower, usando el comando que está en la propia página
del componente, que puedes ver pulsando el icono "+" de la izquierda.
Lanzamos ese comando desde el terminal, situados en la carpeta del proyecto. Con ello se instalará el
componente en la carpeta "bower_components/paper-input".
Ahora vamos a usar ese componente. Para ello vamos al elemento raíz de la aplicación y lo abrirmos para su
edición. En mi caso el elemento raíz está en "test-cli/src/test-cli-app/[Link]".
Si abres ese componente raíz deberías de reconocer algo su código, pues es muy parecido al que hemos
visto en artículos anteriores de este manual de Polymer 2.
El primer paso para usar un componente recién instalado es realizar el correspondiente import, para que el
elemento se conozca en el contexto de la aplicación. Ese import lo puedes colocar al principio del
componente, después del import de la librería Polymer.
Ahora ya podemos usarlo, para lo cual vamos a colocarlo en el template (Dentro de la etiqueta TEMPLATE
del componente raíz).
<template>
<style>
:host {
display: block;
</style>
<h2>Hello [[prop1]]!</h2>
</template>
Ahora puedes refrescar la página para ver los cambios, siempre que tengas el servidor encendido. Si lo
paraste previamente tendrás que hacer de nuevo el "polymer serve".
Para acabar fíjate que ocurre cuando cambias el texto del componente paper-input, que tiene escrito
inicialmente "test-cli-app" (en mi caso, en tu caso debe aparecer el nombre del componente raíz). Si te
cambia el titular H2 de la página es que está funcionando tal como se esperaba. Ese es un ejemplo de
binding, algo que te permite hacer Polymer de una manera muy simple y de lo que ya hemos introducido
algunas pequeñas informaciones, aunque todavía nos toca verlo en detalle.
Espero que lo que has visto hasta ahora te vaya gustando. Aunque lo cierto es que hay mucho todavía que
contar para dominar todo a lo que nos hemos asomado en este artículo, ya que solo hemos jugado un poco
con algunas de las herramientas más simples disponibles en el CLI.
En los siguientes artículos vamos a abordar los conocimientos fundamental para trabajar con Polymer 2.
Veremos cuáles son las utilidades más importantes que nos ofrece la librería para desarrollar, como las
propiedades, el binding de datos y los eventos. Este conocimiento de base luego lo utilizaremos durante
todo el manual, y se aplicará en cualquier componente por sencillo que sea.
También veremos cómo es el ciclo de vida de los componentes, algo que todo desarrollador debe conocer
para poder sacar partido al desarrollo con Web Components, apreciando cómo en muchos de los casos el
propio Javascript estándar es la base de todo lo que nos encontramos dentro de Polymer 2.
Para comenzar en tu aprendizaje de la librería Polymer 2, te ofrecemos ahora una vista de pájaro sobre el
desarrollo de componentes. En este artículo encontrarás ejemplos prácticos de las posibilidades de Polymer
2, en cuanto a la creación de componentes personalizados, que sin duda te ofrecerán una visión general de
muchas de las cosas que puedes hacer con esta librería y muy poco esfuerzo.
Abordaremos diversos conceptos de una manera muy resumida, sin entrar en detalles. Más adelante, a lo
largo de muchos otros artículos de este manual, encontrarás descripciones y explicaciones más detalladas
sobre todos estos conceptos, y muchos otros.
Este artículo surge como un resumen de la clase 02 del Curso de Polymer 2 en EscuelaIT, que hemos
impartido de manera gratuita para todos los interesados en aprender las mejores técnicas de desarrollo
frontend, basado en el estándar de Web Components.
En resumen, los puntos que vamos a tratar en esta clase son los siguientes:
Qué ofrece Polymer 2: Veremos qué es lo que Polymer 2 te ofrece, encima del estándar de los Web
Components y qué tipo de proyectos puedes realizar con Polymer 2.
Flujo de desarrollo de componentes: Veremos la herramienta que se usa en Polymer para el desarrollo de
componentes y aplicaciones, el Polymer CLI junto con Bower (aunque dentro de poco se usará Yarn para
sustituir a Bower), con una breve descripción sobre cómo instalar componentes en un proyecto y cómo
importar las dependencias.
Ejemplos prácticos: En los ejemplos prácticos desarrollados en esta clase encontraremos explicaciones
básicas sobre las tres herramientas más básicas disponibles para el desarrollo de componentes: Propiedades,
Binding y Eventos.
Polymer ofrece muchas herramientas para desarrolladores de componentes. Ofrece una manera de
desarrollar custom elements mucho más ágil y amistosa para el desarrollador, frente a usar sólo Javascript
nativo. En este listado encontrarás básicamente lo que el "core" de Polymer 2 te entrega, encima de Web
Components V1.
Nota: recordando que Polymer 2, el core o núcleo, pesa apenas 12 KB y que existen muchas otras
herramientas ofrecidas por la librería en diversos componentes que se pueden cargar, opcionalmente si
se necesitan, y que ampliarían este listado enormemente. Así pues, nos estamos centrando sólo en lo que
te ofrece el [Link], la clase que usamos para extenderla y crear nuestros propios componentes
Polymer 2.
Los próximos componentes que vamos a usar como ejemplo usan básicamente estas características, así que
nos quedarán mucho más claras en pocos minutos, viendo el vídeo de la clase que encuentras a final de este
texto.
Además de una serie de herramientas para construir fácilmente Custom Elements, Polymer dispone de un
completo catálogo de componentes listos para usar en cualquier tipo de aplicación.
En la Home de Web Components podrás encontrar una clasificación enorme de custom elements listos
para usar en cualquier proyecto, hechos con Polymer, Javascript nativo, o cualquier librería existente.
Además en la misma web encontrarás el catálogo de componentes realizados por el equipo de Polymer.
Con todas estas herramientas te dejan muy sencillo usar Polymer en todo tipo de proyectos,
independientemente de cuál es el stack de tecnologías que se esté usando. Podríamos dividir los casos de
uso en dos clasificaciones:
Componentes para usar en sitios web: Con Polymer 2 puedes usar componentes y esos componentes
serás capaz de implementarlos en cualquier sitio web. Es lo mismo que tu sitio use PHP, WordPress,
Laravel, Python, Django, Node, Java... o lo que sea. Polymer se usa para el desarrollo frontend y es lo
mismo lo que tengas del lado del servidor. Así mismo, los componentes de Polymer se pueden usar junto
con componentes de otros frameworks como Angular, o React. De hecho, Polymer no busca sustituir a una
de estas librerías (aunque en la práctica sí podrías), sino servir de complemento estándar para todo tipo de
componente que podrías usar en cualquier tipo de sitio web.
Desarrollo de aplicaciones frontend completas: Con Polymer podemos hacer completas aplicaciones
web de última generación, o Progressive Web Apps. En general con Polymer y usando diversos
componentes del catálogo, te ponen muy fácil desarrollar aplicaciones SPA (Single Page Application) o
aplicaciones progresivas, simplemente juntando bloques.
En esta clase te enseñaremos también cuál es el flujo de desarrollo de aplicaciones con Polymer 2.
Explicaremos asuntos como el Polymer CLI (las herramientas de línea de comandos disponibles en Polymer
para crear nuevos proyectos y componentes) y Bower. Verás también cómo gestionar dependencias en
proyectos, partiendo desde cero.
Como esto es material de otros artículos, te recomendamos ver la clase o explorar el Manual de Polymer 2
para encontrar más información detallada.
Básicamente lo que vamos a construir es un par de componentes que colaboran entre sí para realizar algo en
común. Nuestro objetivo final es mostrar un listado de instrumentos musicales y para ello tendremos dos
componentes.
Componente listado de instrumentos: Realizará una llamada a un API REST para obtener un
listado de instrumentos musicales que mostrar, por medio de una solicitud GET con Ajax. El
listado que el API REST entrega es en sí un array, que por medio de un template de repetición, se
mostrará en la página. Para mostrar cada uno de los instrumentos del listado nos apoyaremos en
otro componente creado por nosotros.
Componente instrumento musical: el componente del listado se apoyará en el componente del
instrumento. Cada uno de los instrumentos musicales se presentarán con un componente
Lo que veremos en resumen en la clase nos ayudará a ver asuntos clave en el desarrollo de componentes
Polymer 2 como el binding, la declaración de propiedades, eventos, el uso de componentes adicionales del
catálogo de Polymer, la colaboración entre componentes para dividir las tareas por responsabilidades, etc.
Ahora dejamos el código de los componentes desarrollados durante la clase, para que quede como
referencia a cualquier persona interesada.
Componente instrumento-musical
Este es el componente que permite mostrar una tarjeta de un instrumento musical, es decir, cada uno de los
item del listado.
<dom-module id="instrumento-musical">
<template>
<style>
:host {
display: block;
border-radius: 6px;
font-family: 'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif;
margin-bottom: 10px;
h1 {
font-size: 1.4em;
margin: 0;
color: #f52757;
p {
font-size: .9em;
color: #2055a8;
p span {
font-weight: bold;
</style>
<h1 on-click="toggle">[[nombre]]</h1>
<iron-collapse opened="[[desplegado]]">
<p on-click="resaltarDescripcion">[[descripcion]]</p>
<p><span>Clasificación:</span> [[clasificacion]]</p>
</iron-collapse>
</template>
<script>
return 'instrumento-musical';
return {
nombre: String,
clasificacion: {
type: String,
},
descripcion: String,
desplegado: {
type: Boolean,
value: false
};
resaltarDescripcion(e) {
[Link](e);
[Link] = [Link]();
toggle() {
[Link] = ![Link];
[Link]([Link], InstrumentoMusical);
</script>
</dom-module>
Componente listado-instrumentos
Este es el código del componente que hace el listado de instrumentos musicales, que ha obtenido por medio
de una conexión Ajax a un API REST. Se apoya en el componente anterior para realizar la tarea.
<dom-module id="listado-instrumentos">
<template>
<style>
:host {
display: block;
</style>
<iron-ajax
id="ajax"
url="[Link]
last-response="{{instrumentos}}"
loading="{{cargando}}"
></iron-ajax>
<instrumento-musical
nombre="[[[Link]]]"
descripcion="[[[Link]]]"
clasificacion="[[[Link]]]"
></instrumento-musical>
</template>
</template>
<script>
return 'listado-instrumentos';
return {
cargando: Boolean,
instrumentos: {
type: Array,
value: function() {
return [];
};
obtener() {
this.$.[Link]();
[Link]([Link], ListadoInstrumentos);
</script>
</dom-module>
Otra cosa que necesitarás para realizar estos ejemplos es el JSON que usamos para implementar el API
REST. Estamos usando JSON Server, que nos permite crear un sistema REST en local, que es un paquete
que explicamos con detalle en el artículo de JSON Server.
Este es el aspecto de nuestro JSON, que no reproduzco entero pues realmente lo que te interesa es la
estructura de los datos.
"instrumentos": [
"nombre": "Guitarra",
"clasificacion": "cuerda",
"descripcion": "Instrumento musical de cuerda, con una caja de resonancia y seis cuerdas en un mástil..."
},
"nombre": "Tambor",
"clasificacion": "percusión",
"descripcion": "Instrumento de naturaleza variada que permite golpear una membrana que producen el sonido..."
Por último, te dejamos el vídeo de la clase completa de introducción al desarrollo de componentes con
Polymer 2. Esperamos que puedas aprovecharlo y seguir la práctica realizada en clase para enfrentarte al
desafío de crear tus primeros componentes personalizados.
Ahora que ya conocemos la estructura básica de un elemento Polymer 2 podemos centrarnos en aprender a
trabajar con sus propiedades, que nos permitirán manejar el estado de los componentes.
Los elementos realizados hasta este punto en el Manual de Polymer 2, solo tenían algo de HTML y unos
simples estilos. Ahora vamos a darles algo más de sentido gracias a las propiedades, que pueden almacenar
valores que podrán variar a lo largo del tiempo. Además, gracias al data binding, podemos conseguir que el
componente cambie de estado al cambiar las propiedades, sin invertir más líneas de código.
Las propiedades deben declararse en la clase de implementación del componente. Para indicarlas usaremos
un getter, de manera parecida a lo que hacíamos al definir la propiedad "is".
Se definen devolviendo un objeto con todas las propiedades que nuestro componente vaya a necesitar.
Podemos especificar muchas cosas al declararlas y gracias a ello podrán tener distintos comportamientos
que nos facilitarán diversas situaciones. Justamente esta riqueza en la declaración de propiedades es una de
las ventajas de usar Polymer, con respecto a lo que nos ofrece el Javascript nativo de Web Components.
return 'estado-animo';
return {
animo: {
type: String,
value: 'feliz'
[Link]([Link], EstadoAnimo);
En este código has visto cómo se define una propiedad de tipo cadena de caracteres y a la cual hemos
inicializado con un valor determinado.
Otra de las ventajas de Polymer es que, mediante una simple sintaxis en el template, podemos volcar el valor
de una propiedad en la representación del componente.
En nuestro template simplemente usamos dos llaves para volcar el contenido de una propiedad del
componente.
<template>
<style>
span {
text-transform: uppercase;
</style>
<div>
</div>
</template>
Fíjate que al referirnos a una propiedad simplemente tenemos que indicar su nombre {{animo}} y el
contenido de ella se colocará en el propio HTML del componente. Además, si la propiedad cambia en
cualquier momento, Polymer se encargará de actualizar el template para representar el nuevo estado, sin que
tengamos que intervenir de alguna manera.
Nota: Existen diversos tipos de binding, de una o dos direcciones. De momento nos vamos a olvidar de
esa parte, aunque a decir verdad en este template poco importa, porque al volcar texto en una etiqueta
HTML básica, el dato no tiene como viajar más que en una dirección.
Otra de las cosas básicas que debemos saber de las propiedades es que en los componentes se pueden
colocar atributos y asignarles desde fuera valores.
El ánimo predeterminado, definido al declarar la propiedad "animo" era "feliz", pero cuando estemos
usando este componente podremos asignarle cualquier estado de ánimo que nos venga en gana, o que
necesitemos para nuestra aplicación.
<estado-animo animo="sorprendido"></estado-animo>
Los atributos definidos fuera viajan dentro del componente y modifican su estado. Pero como además todo
esto es en el fondo Javascript nativo, si manipulamos el DOM con Javascript, también se alterará el
componente.
Como sabes, puedes cambiar el valor de un atributo a través del método setAttribute() y con ello veremos
que el nuevo valor viaja hacia dentro en el componente.
En este pequeño código hemos jugado algo con esta posibilidad y hemos creado un par de botones para
manipular desde fuera el estado de las propiedades del componente, asignando nuevos estados de ánimo,
que al cambiar alterará la representación del elemento.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<script>
function ponerContento(){
[Link]('displayanimo').setAttribute('animo', 'contento');
function ponerTriste(){
[Link]('displayanimo').setAttribute('animo', 'triste');
[Link]('botoncontento').addEventListener('click', ponerContento);
[Link]('botontriste').addEventListener('click', ponerTriste);
</script>
</body>
</html>
Si lo deseas, puedes aprender algunas de las cosas que hemos explicado en este artículo en el siguiente
videotutorial. En el vídeo podrás encontrar una breve explicación de las propiedades declaradas, junto con
un ejemplo de declaración de propiedades en un componente. Verás que las propiedades una vez declaradas
las podemos usar en el template y aprenderás a inicializar las propiedades, cargando los valores a través de
atributos en la etiqueta del componente.
Conclusión
Hemos conocido de manera resumida las propiedades de los elementos de Polymer. Trabajar con ellas es
realmente sencillo y nos permiten hacer gran cantidad de cosas realmente útiles.
Como hemos dicho, existen diversos tipos de propiedades, y no solo nos referimos a que tengan un tipo de
datos o de otro, sino que posean comportamientos especiales que nos ayuden a resolver determinadas
situaciones. Por ejemplo existen las propiedades computadas, que usan una función para resolver su valor,
las propiedades capaces de notificar al padre de los cambios, etc. Además existe la posibilidad de observar
valores de las propiedades para implementar programación reactiva. Todo esto lo iremos viendo con calma
más adelante, en próximos artículos, aunque si vienes de Polymer 1 estamos seguros que te podrás ir
haciendo una idea.
En la siguiente entrega del Manual de Polymer 2 podrás encontrar una información muy útil sobre el ciclo
de vida de los componentes (del estándar Web Components V1 y los que agrega Polymer 2).
Para el desarrollo basado en componentes, el estándar de Web Components V1 ha establecido una serie de
métodos que nos permiten realizar tareas a medida que ocurren ciertas cosas con los elementos
personalizados, como su creación, inserción en la página, eliminación de la página, etc. Todos estos estados
por los que pasa un componente se llaman el ciclo de vida y es algo que debemos conocer cuando vamos a
trabajar con Polymer 2.
Para cada estado se puede asociar lo que se llama una "función callback", que contiene el código que
queremos ejecutar como respuesta a ese cambio en el estado del elemento. En este artículo vamos a
conocer los diversos métodos relacionados con el ciclo de vida, las funciones callback que podemos asociar
a cada uno de ellos y ejemplos que nos ayudarán a comprenderlo todo mucho mejor.
La mayor parte de los estados del ciclo de vida de un componente Polymer 2 son derivados directamente
del estándar Web Components V1. Es decir, el propio Javascript nativo, el entendido por los navegadores,
es el que nos ofrece mayormente las funciones callback que podemos asociar a un componente.
Los estados del componente a lo largo del ciclo de vida son los siguientes:
constructor: Es el propio constructor de la clase ES6 que se escribe para implementar un custom
element. El constructor se invoca una vez por cada elemento singular creado de un tipo. Es decir, si
tenemos 3 instancias (etiquetas, elementos) del custom element "mi-elemento-personalizado", el
constructor se llamará 3 veces, uno por cada uno.
connectedCallback: Este estado del ciclo de vida ocurre cuando el elemento se inyecta en el DOM
de la página.
disconnectedCallback: Este estado ocurre cuando el elemento se borra del DOM de la página.
attributeChangedCallback: Este momento del ciclo de vida de los componentes se produce cada
vez que un atributo o propiedad del componente cambia.
Nota: Estos callbacks para hacer cosas durante el ciclo de vida son opcionales. Es decir, si no los
escribes en la implementación del componente simplemente no se realizarán ningunas acciones
específicas en tales momentos.
La mayoría de las veces podrías observar que, al usar un componente, se ejecuta el constructor y
seguidamente el connectedCallback, pero no tiene por qué ser así. Para entenderlo tienes que conocer los
tres mecanismos por los que podríamos crear un componente:
En este caso el constructor del componente se ejecuta y enseguida el connectedCallback, puesto que el
elemento se construye y se inyecta en el contenido de la página en el mismo paso.
<mi-custom-element></mi-custom-element>
Este método de Javascript nativo crea un elemento en la página, que podría ser cualquier elemento del
HTML, pero también un elemento personalizado. En este caso el elemento se crea en la memoria de
Javascript, pero no se inyecta en la página, así que solo se ejecutaría el constructor.
Ya que los custom elements se definen a partir de clases, nada nos impide crear el componente mediante el
operador "new", igual que hacemos al instanciar objetos en la programación orientada a objetos de toda la
vida. En este caso el componente también se crea sólo en la memoria de Javascript y por ello solo se
ejecutaría el constructor.
Nota: Sobra decir que en este caso usamos el nombre de la clase y no el nombre de la etiqueta, puesto
lo que estamos haciendo es instanciar un elemento de la clase.
Tanto el el caso 2 como en el 3 descritos arriba, si deseas inyectar en la página esos componentes, creados
tan solo en la memoria de Javascript, tendrías que usar los mecanismos conocidos del propio lenguaje para
la manipulación del DOM, como el método appendChild(). En el momento que inyectes ese componente es
en el que se ejecuta finalmente el correspondiente método connectedCallback. Luego veremos ejemplos de
todos estos casos.
Además de los anteriores métodos callback, Polymer 2 agrega uno adicional denominado "ready", que
seguramente conozcan los desarrolladores que vienen de usar Polymer en su versión 1.x.
ready: este callback para definir acciones durante el ciclo de vida se denomina "One-time
initialization" en la documentación de Polymer 2. Básicamente es como un "connectedCallback",
solo que no se ejecuta más que 1 vez, la primera que se inyecta en el DOM de la página.
La diferencia entonces entre connectedCallback y ready es que un elemento particular podría producir
muchos connectedCallback pero un único ready. Esto podría ocurrir en el caso que un determinado
elemento se coloque en el DOM, luego se quite y se vuelva a colocar. Cada vez que se vuelva a colocar se
ejecuta de nuevo connectedCallback, pero el ready solo se ejecutará la primera vez que se inyectó en la
página.
Nota: "One-time initialization" podría prestarse a confusión, pensando que solo se inicializa una vez,
independientemente del número de instancias del componente. Eso sería incorrecto. Es decir, puedes
tener un número indeterminado de elementos de un custom element determinado. Ready se ejecuta una
tantas veces como número de elementos que hayas creado e incluido dentro del documento o DOM de
la página.
Es importante que, en todo método callback del ciclo de vida de Polymer, se invoque siempre al método
correspondiente, pero en la clase padre, por medio de "super".
Solo que en el caso de los métodos callback la llamada a super se realiza con el objeto super y seguido del
método callback que corresponda, por ejemplo: [Link]() o [Link](). Lo verás más
tarde en un ejemplo en este mismo artículo.
Otra cosa que debes saber sobre los callbacks del ciclo de vida en Polymer está relacionada con el
"attributeChangedCallback", ya que en Polymer 2 sólo se invocará ese callback con las propiedades que
hayas declarado en el objeto "properties". Dicho de otro modo, si el componente sufre un cambio en uno
de sus atributos del HTML no se invocará attributeChangedCallback en el caso que ese atributo no sea una
de sus propiedades declaradas.
Ahora vamos a ver un componente en el que se han implementado todos los callbacks del ciclo de vida que
acabas de conocer. En este componente no se hace nada en particular, solo lo tenemos para ir mandando
mensajes a la consola cada vez que el componente cambia de estado dentro de su ciclo de vida.
<dom-module id="ciclo-vida">
<template>
<style>
:host {
display: block
</style>
</template>
<script>
return 'ciclo-vida';
return {
test: String
};
constructor() {
super();
[Link]('Ejecutándose el constructor')
connectedCallback() {
[Link]();
[Link]('Ejecutándose connectedCallback');
disconnectedCallback() {
[Link]();
[Link]('Ejecutándose disconnectedCallback');
[Link]();
ready() {
[Link]();
[Link]('Ejecutándose ready');
[Link]([Link], CicloVida);
</script>
</dom-module>
No debe pasar desapercibido el detalle de las invocaciones a "super" en los callbacks y la declaración
"properties", con la propiedad "test", que es necesaria para que el attributeChangedCallback se llegue a
ejecutar. Si te interesa saber algo más sobre esta llamada a super, por favor, consulta el artículo sobre
herencia en las clases de Javascript ES6.
Para poder ejecutar este componente y asegurarnos que los distintos estados del ciclo de vida se produzcan,
a su tiempo y de manera que podamos percibir cambios en el componente y los mensajes que ocurren,
tenemos que usar el componente con una página HTML que podría tener una forma como esta:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Uso del componente ciclo-vida y botones para poder producir cambios en el estado</title>
</head>
<body>
<h1>
</h1>
<p>Se colocan dos elementos en el body y se generan dos elementos en la memoria de Javascript.</p>
<p>Pulsa además los botones para poder producir cambios en el estado de un elemento y ver los otros mensajes del ciclo de vida.</p>
<ciclo-vida></ciclo-vida>
<ciclo-vida></ciclo-vida>
<script>
[Link]('id', 'el1');
[Link]('anadirAlDOM').addEventListener('click', function() {
[Link](el1);
});
[Link]('eliminarDelDOM').addEventListener('click', function() {
[Link]([Link]('el1'));
});
[Link]('cambiarAtributo').addEventListener('click', function() {
});
</script>
</body>
</html>
Te recomendamos poner en marcha estos ejemplos en tu propio proyecto o carpeta de trabajo, para
apreciar el funcionamiento y asimilar el conocimiento contenido en este artículo. Recuerda seguir el Manual
de Polymer 2 para obtener más información sobre el desarrollo con esta estupenda librería basada en Web
Components.
Binding en polymer 2
Qué es el binding y cómo lo configuramos en Polymer 2. Tipos de binding bidireccional y
unidireccional. Interoperabilidad entre componentes.
En este artículo del Manual de Polymer 2 vamos a abordar el binding en Polymer 2. Como a estas alturas
muchos lectores tendrán ya claro el concepto de binding, vamos a comenzar directos al grano, ofreciendo
unas pequeñas y rápidas anotaciones para explicar cuál es el funcionamiento general del binding en Polymer
2 y su sintaxis.
Pero si tú todavía no tienes claro qué es el binding no te preocupes, porque en el resto del artículo
ahondaremos en los conceptos para no dejar ni un detalle y de modo que puedas resolver todas tus dudas.
Así pues, vamos a comenzar con explicaciones objetivas y rápidas, que sirvan de referencia para todo el
mundo.
El binding en Polymer funciona siempre transmitiendo los datos de padres a hijos. Un componente puede
transmitir el valor de una de sus propiedades a cualquier elemento del template, así como a otros
componentes que tenga como hijos.
Además el binding es configurable, permitiendo definir si se desea un binding de una dirección o de dos
direcciones. El binding de una dirección es el que transmite de padres a hijos y el binding de dos direcciones
es el que además transmite el valor en dirección contraria: desde el hijo hasta el padre.
Para definir si queremos binding de una dirección y de dos direcciones, utilizamos dos tipos de notaciones
distintas:
La notación de los dobles corchetes "[[]]" significa que se debe implementar binding de una sola
dirección. En este caso el valor bindeado viaja del padre al hijo pero no del hijo al padre.
La notación de las dobles llaves "{{}}" significa binding de dos direcciones. Es decir, el valor
bindeado viaja del padre al hijo y también del hijo al padre.
Nota: Ojo con el binding de dos direcciones, puesto que es un poco más pesado para la aplicación, en
términos de tiempo de procesamiento. Por eso en muchas ocasiones es preferible usar bindeo de una
sola dirección y usar mecanismos como los eventos para producir el aviso a los padres cuando los hijos
desean cambiar el valor de las propiedades. Hablaremos de esto más adelante en otros ejemplos cuando
hayamos abordado el tema de los eventos en Polymer.
De momento es todo lo que te vamos a contar en el presente artículo sobre el binding en Polymer. Solo
debes saber además que para que se produzca un binding ascendente en Polymer, aparte de usar las dobles
llaves, necesitas que el componente que debe avisar al padre tenga definida la propiedad con el valor
"notify" a "true". Hablaremos de esto más adelante también con otros ejemplos.
Nota: Para que quede claro, con respecto al binding de dos direcciones, debes entender que por mucho
que tú lo uses al declarar el bindeo en el template, Polymer no siempre lo implementará y te hará un
binding de una única dirección. Por ejemplo, también te ocurrirá cuando bindeas al contenido de una
etiqueta HTML párrafo (etiqueta P). Por mucho que uses dobles llaves no servirá para nada, ya que ese
párrafo no está preparado para avisar al padre hipotéticos cambios.
Si no tienes claro el concepto de binding o deseas ver ejemplos de binding en Polymer, sigue leyendo.
Qué es el binding
El binding es una herramienta base de muchas librerías y frameworks. Simplemente es un enlace, para
asociar un mismo dato a diversos integrantes de una aplicación, de tal modo que si uno de ellos lo cambia,
su valor cambie en los otros elementos que tienen ese dato bindeado.
El binding nos ahorra programar más de la cuenta, pues el propio Polymer será el encargado de hacer viajar
el dato bindeado de un lugar a otro. Esto evita que tengamos que estar suscribiendonos a eventos, para
saber en qué momento se modifica un dato y así realizar la operación de escritura manualmente en los
elementos u objetos que lo comparten.
Básicamente encontraremos binding en dos situaciones. Prefiero verlas por separado, aunque realmente
podríamos decir que es lo mismo con distintos matices.
El caso más sencillo de binding se produce desde las propiedades de un componente hacia el template. Este
ejemplo ya lo hemos observado en casos anteriores del Manual de Polymer 2, como en el componente del
estado de ánimo del artículo de Propiedades y estado de los componentes en Polymer 2.
return {
propiedadComponente: {
type: String,
};
<template>
<p>
</p>
</template>
Nota: La notación de los corchetes "[[]]" en este caso produce un binding de una sola dirección.
Aunque en este caso, al bindear a un texto de un template realmente no podría existir binding de dos
direcciones, por lo que tampoco tendría mucho sentido usar las dobles llaves.
Además puede darse el binding entre diversos componentes. Por ejemplo, lo tenemos aquí:
<template>
<estado-animo animo="[[miAnimo]]"></estado-animo>
</template>
En este template tenemos dos ejemplos de binding entre componentes y en uno hemos usado bindeo de
una dirección y en otro bindeo de dos direcciones. Todo tiene su sentido.
Por un lado tenemos un paper-input, que es un campo de texto donde se puede escribir una cadena de
caracteres. El valor del campo de texto se está escribiendo en la propiedad "miAnimo", por medio de un
binding escrito sobre el atributo "value" del paper-input.
Nota: en este caso, con la notación de las dobles llaves "{{}}" estamos produciendo un binding de dos
direcciones: value="{{miAnimo}}". Aquí es necesario el binding de dos direcciones para que, cuando
alguien escriba algo en el campo de texto, el valor nuevo que se ha escrito viaje automáticamente al
padre.
Como segundo ejemplo de binding tenemos la propiedad "miAnimo" bindeada al componente "estado-
animo". Eso indica que, cada vez que cambie "miAnimo" porque el usuario escriba otra cosa en el paper-
input, se actualizará el valor bindeado al componente "estado-animo".
Nota: en este nuevo caso tenemos un binding de una dirección: animo="[[miAnimo]]". En este caso es
suficiente de una dirección porque el componente estado-animo no tiene posibilidad de cambiar el valor
de la propiedad bindeada. Y aunque lo cambiase, el componente padre no necesita enterarse de nada.
Para acabar, vamos a ver el código completo del componente que hemos creado para explicar el binding.
En realidad es una modificación del ejemplo que explicamos en el anterior artículo, donde hablamos sobre
la instalación de Polymer 2 y su CLI.
<dom-module id="test-cli-app">
<template>
<style>
:host {
display: block;
</style>
<estado-animo animo="{{estadoAnimo}}"></estado-animo>
</template>
<script>
return {
estadoAnimo: {
type: String,
};
[Link]([Link], TestCliApp);
</script>
</dom-module>
Obviamente, un tema tan importante como el binding incluye muchas otras cosas que no hemos abordado
en este artículo. Pero no te preocupes, con lo que hemos visto aquí podremos ir funcionando bien, hasta
que lleguemos a un nivel más avanzado en el manual de Polymer 2.
Más adelante en la sección de binding podremos aprender muchas otras cosas interesantes, como hacer
componentes que avisen a los padres de cambios en sus propiedades, bindear a atributos nativos de
elementos de HTML, etc.
El mundo de los eventos en Polymer es bastante rico y además ha cambiado en algunas cosas en lo que
respecta a su versión predecesora, por lo que tendremos que prestar bastante atención incluso si venimos de
Polymer 1.x. La novedad principal es que en Polymer 2 se usa más el propio Javascript nativo para cosas en
las que antes existían mecanismos proveídos por la propia librería (Recuerda: "UseThePlatform").
Sin embargo, en este artículo vamos a observar algunas cosas sencillas sobre eventos, que nos permitan
tener las bases para ir realizando ejemplos, en lo sucesivo del Manual de Polymer 2, en los que podamos
usar distintos tipos de eventos y asociar manejadores. En resumen, nos centraremos por ahora en la
declaración de los eventos dentro del propio template y usaremos eventos básicos, puesto que los eventos
de gestos requieren cargar mixins, de los que todavía no hemos hablado.
Además, para los que vienen de Polymer 1.x ofreceremos algunas de las claves principales que deben
conocer para saber qué diferencias encontrarán en el tratamiento de eventos en la versión 2 de la librería.
A la hora de escribir un template podemos asociar una función manejadora de evento en cualquier elemento
del shadow dom del componente. Para ello simplemente usaremos un atributo "on-", seguido del nombre
del evento que queramos definir su manejador. Ese atributo tendrá como valor el nombre del método
manejador del evento que se desea asociar.
Por ejemplo, encontraremos un código como este para definir un manejador de evento frente a un clic
sobre un botón.
El manejador del evento en un componente de Polymer corresponde con un método de la propia clase del
componente. El método lo podemos definir como cualquier otro método dentro de la clase ES6.
incrementaClic() {
El manejador recibe el objeto evento, del que podemos obtener información diversa sobre el tipo de evento
que se acaba de producir. Ese objeto evento será siempre el primer parámetro del manejador.
incrementaClic(e) {
[Link]('Objeto evento', e)
Ahora vamos a ver el código de un componente sencillo para aprender a trabajar con lo básico de eventos.
Este componente me informa de las veces que he hecho clic en un botón, por medio de un contador que se
incrementa con cada clic. Además encontraremos otro botón que permite resetear el contador, volviendo su
valor a cero.
<dom-module id="cuenta-clics">
<template>
<style>
:host {
display: block;
padding: 5px;
margin-bottom: 5px;
p {
margin-top: 0;
padding: 5px;
background-color: darkorange;
color: white;
</style>
<p>
</p>
</template>
<script>
return 'cuenta-clics';
return {
contador: {
type: Number,
value: 0
constructor() {
super();
incrementaClic(e) {
[Link]++;
[Link]('Objeto evento', e)
resetear() {
[Link] = 0;
[Link]([Link], CuentaClics);
</script>
</dom-module>
De momento vamos a quedarnos con lo aprendido hasta este momento, pero para que no te sientas perdido
si vienes de Polymer 1.x quiero ofrecer algunas otras claves:
En Polymer se recomienda usar el propio Javascript para crear tus eventos. Por ejemplo, si quieres crear un
evento que afecte a todo el host (el componente completo, la etiqueta que engloba todo el Shadow DOM
que podamos tener dentro del elemento personalizado) podrías crearlo directamente con
[Link]().
En este código vemos cómo definir el evento sobre el host, asociando el manejador en el método callback
ready.
ready() {
[Link]();
[Link]('click', () => {
})
Eventos de gestos
Los eventos de gestos ahora no están incluidos "de casa", por eso no te funcionará el evento on-tap en un
componente básico. Hay un mixin llamado [Link] que da soporte a los eventos de
gestos y que tendremos que heredar en el componente donde los queramos usar.
Nota: Los mixins sirven para hacer lo que sería algo parecido a la herencia múltiple. Básicamente sirven
para heredar código sin necesidad de que el mixin esté en el extends. Son necesarios porque Javascript
no tiene herencia múltiple. Pero lo más importante que ya te adelantamos también, los mixins en
Polymer 2 son los sustitutos a lo que conocíamos por "behaviors" en Polymer 1.x.
Como decía, no me quiero meter todavía en este tema, pero es bueno que se sepa ya. Encuentras más
información de momento en la documentación.
Eliminado [Link]()
Otra cosa que echarás en falta si vienes de Polymer 1 es que se ha eliminado el método fire que existía en
todos los elementos de Polymer, útil para lanzar eventos personalizados. Ahora usas el método nativo de
Javascript dispatchEvent. Más adelante pondremos ejemplos.
Conclusión
Como ves, lo básico de eventos es muy sencillo, ya que podemos definir los eventos de manera declarativa y
los métodos manejadores son también muy rápidos de implementar. En ellos tenemos acceso a todas las
propiedades y recursos del componente para poder reaccionar de la manera que haga falta.
Sin embargo, nos hemos quedado solamente en lo fundamental y además hay diferentes usos necesarios de
aprender en Polymer 2, sobre los que tendremos que volver en el futuro en varios artículos y ejemplos.
Nuestro objetivo ahora era simplemente dar unos conocimientos fundamentales para poder apoyarnos en
ellos en futuros ejercicios y artículos. Os pedimos un poco de paciencia porque todo se verá a su debido
tiempo.
En la próxima serie de artículos vamos a detallar cómo funciona el sistema de propiedades en componentes
desarrollados con Polymer 2. Estudiaremos asuntos importantes para poder crear nuestros custom elements
sacando partido a las posibilidades de la librería, como la serialización y deserialización de propiedades a
atributos, los tipos de propiedades disponibles en el librería, junto con ejemplos destacados de componentes
donde seguir practicando.
En Polymer 2, de manera similar a su predecesor Polymer 1, existe la posibilidad de trabajar de una manera
ágil con propiedades de los componentes, así como asignar valores desde fuera de éstos, de manera
declarativa en el propio HTML. Ésto no debe ser nuevo para ti, puesto que las propiedades son una de las
principales herramientas con las que contamos para el desarrollo basado en componentes y ya hemos
trabajado con ellas anteriormente.
En esta ocasión vamos a ahondar sobre el tema de las propiedades, explicando los tipos de datos que
puedes asignarles y cómo se deserializan valores en las etiquetas de los componentes. Recuerda consultar el
artículo de introducción a las propiedades y estado de los componentes para encontrar información más
básica y general.
Al declarar propiedades podemos indicar el tipo de datos que vamos a usar en esa propiedad, de modo que
en el momento de su creación Polymer se ocupará por asignar ese tipo a la variable.
Como sabes, Javascript es un lenguaje con tipado dinámico, por lo que una variable puede cambiar de tipo
con el tiempo, ya sea porque el programador lo requiera o porque algún despiste en el código produzca ese
cambio de tipo en el tiempo de ejecución. De modo que Polymer nos asegurará que la propiedad tenga
inicialmente un tipo, pero no puede asegurarnos que lo mantenga durante toda la ejecución de la aplicación,
pues es algo que depende más de cómo la hayas programado.
En el siguiente código tenemos dos propiedades declaradas, miNumero y miString. Esas propiedades
tendrán los tipos definidos en la declaración que puedes ver a continuación.
return {
miNumero: Number,
miString: {
type: String,
},
Al declarar las propiedades podemos simplemente indicar el tipo, como es el caso de "miNumero" en el
código anterior. Pero, si además queremos declarar otras cosas sobre la propiedad, como su valor por
defecto, una función observadora, etc., entonces necesitas pasar a una declaración compuesta en notación
de objeto, como el caso de "miString".
Para entendernos, ahora vamos a llamar atributos a los valores que se colocan en una etiqueta HTML de un
componente, como por ejemplo "mi-atributo" en el siguiente código:
Una propiedad es algo interno del componente y Polymer nos ofrece de manera automática el traspaso del
valor de un atributo en su correspondiente propiedad.
Solo debes tener en cuenta que en HTML no existe distinción entre mayúsculas y minúsculas, y sin embargo
Javascript sí que lo tiene en cuenta. Por ello al trabajar con nombres de atributos y propiedades hay que ir
con cuidado especial.
La propiedad, al ser algo del lenguaje Javascript, se suele escribir con notación "CamelCase", colocando las
primeras letras de la segunda y siguientes palabras en mayúscula. Para adaptar el CamelCase en el HTML se
utilizan guiones, separando las distintas palabras. Por ejemplo la propiedad "miNumero" se correspondería
con el atributo "mi-numero" en el HTML.
Nota: Es un error frecuente olvidarse los guiones en el HTML y escribir los atributos con CamelCase.
En este caso no se realizará la correspondencia a la propiedad con su nombre CamelCase, sino a su
nombre con minúsculas. Por ejemplo el atributo HTML "ciudadOrigen" se haría corresponder con la
El proceso por el que se traspasan los valores del HTML a las propiedades es la deserialización y en él se
intentará corresponder el valor del atributo con el tipo de dato indicado en la declaración de la propiedad.
Esto es importante porque todos los datos colocados como valores de atributos en el HTML son cadenas
de texto. HTML no distingue si lo que se ha colocado como valor de un atributo es una cadena, un número
o un boleano y sin embargo, Polymer nos hará el trabajo de corresponder y transformar aquella información
al tipo correcto.
Cadenas (String): Aquí Polymer no realiza ningún trabajo especial, puesto que los valores de los
atributos son siempre cadenas.
Números (Number): Polymer se encarga de convertir la cadena en un valor numérico, haciendo lo
posible porque esa conversión sea lógica. Convierte por ejemplo la cadena "4" por el número 4.
Convierte la cadena "092" por el número 92. Aunque algo como "x665" no lo podría convertir en
un número y otorgará a la propiedad el valor "NaN" (not a number).
Boleanos (Boolean): En este caso no importa el valor del atributo, sino simplemente su presencia.
En el caso que el atributo se encuentre en la etiqueta, entonces la propiedad tendría valor asignado
como "true". Si el atributo no está presente, la propiedad nacerá con el valor "false" (a no ser que se
le indique otro valor predeterminado).
Arrays y objetos (Array / Object): Para estos dos tipos de datos los valores de los atributos se
tienen que indicar con notación JSON. En este caso es importante tener en cuenta que en un JSON
las comillas válidas son las comillas dobles (") y por tanto no nos queda otro remedio de colocar el
valor del atributo HTML con comillas simples. Luego verás un ejemplo.
Fechas (Date object): Polymer tratará de convertir la fecha en un objeto de la clase Date de
Javascript. Ten en cuenta que debes usar un formato de fecha apropiado como valor de atributo,
como 2017-09-01.
Con lo aprendido en este artículo vamos a mostrar el código de un componente que podríamos tener para
practicar con diferentes tipos de propiedades.
Nota: Encontrarás algunas cosas de las que no hemos hablado todavía, como un template de repetición,
que usamos para recorrer los elementos de un array. La declaración de eventos diversos. O un $
misterioso en un binding sobre un atributo hidden (hidden$) que en este caso sirve para ocultar o
mostrar elementos dependiendo de una propiedad boleana.
>
<dom-module id="deserializacion-tipos">
<template>
<style>
:host {
display: block
</style>
<div on-click="mostrarTipoMiString">[[miString]]</div>
<div on-click="mostrarTipoMiNumero">[[miNumero]]</div>
[[item]]
</template>
<div>
</div>
</template>
<script>
return 'deserializacion-tipos';
return {
miString: {
type: String,
},
miNumero: {
type: Number
},
miBoleano: Boolean,
miArray: Array,
miObjeto: Object,
miFecha: Date
};
constructor() {
super();
mostrarTipoMiNumero() {
mostrarTipoMiBoleano() {
mostrarTipoMiString() {
mostrarTipoMiFecha() {
[Link]([Link], DeserializacionTipos);
</script>
</dom-module>
En el componente se agregaron unos eventos "click" que nos sirven para mandar mensajes a la consola en
los que mostramos el tipo de datos que tienen las propiedades en el componente.
Ahora puedes apreciar el código que tendríamos para usar este componente, enviando los valores de los
atributos en el código HTML. Realmente no tiene mucho misterio, pero sobre todo resulta interesante ver
cómo se han definido los valores de tipo array y tipo objeto en formato JSON.
<deserializacion-tipos
mi-numero="090"
mi-boleano
mi-fecha="1975-02-21"
></deserializacion-tipos>
Nota: Recuerda que para funcionar tendrías que importar como siempre el componente, usando el
correspondiente "import".
Insistimos en tomar en cuenta las comillas dobles en un JSON. Observa cómo el atributo "mi-objeto" tiene
comillas simples para indicar su valor. En el caso del valor de "mi-array", aunque el valor es otro JSON, nos
hemos tomado la licencia de usar comillas dobles, dado que no hemos necesitado usar cadenas dentro del
JSON.
Vamos a suponer que en un componente debemos reconocer los tipos "Date" con valores que admitan el
formato de fechas en español. En este caso no nos sirve el deserializador de Date disponible, por lo que
podemos sobreescribirlo, rescribiendo simplemente el método de Polymer _deserializeValue(value, type).
Lo vamos a ver con un ejemplo de un nuevo componente:
<dom-module id="deserializacion-personalizada">
<template>
<style>
:host {
display: block
</style>
</template>
<script>
return 'deserializacion-personalizada';
return {
miFechaEspanola: Date
};
constructor() {
super();
_deserializeValue(value, type) {
if (type == Date) {
return [Link](value);
fechaEspanolaToDate(valor) {
mostrarTipoMiFechaEspanola(){
[Link]([Link], DeserializacionPersonalizada);
</script>
</dom-module>
En este componente tenemos una propiedad llamada "miFechaEspanola" que es de tipo Date.
Al sobreescribir _deserializeValue() preguntamos si el tipo es Date, en cuyo caso lo dirigimos por una
función propia, que en este caso hemos llamado fechaEspanolaToDate(). Esa función se encargaría de
realizar el trabajo necesario, devolviendo el valor deserializado.
Incluso podríamos crearnos nuestro propio tipo personalizado con una clase ES6, por ejemplo:
class TipoPersonalizado {
//aquí el código de un nuevo tipo inventado, definido por medio de una clase ES6
Esa clase ahora nos permite definir propiedades de este tipo personalizado.
return {
miFechaEspanola: TipoPersonalizado
};
Y ahora para deserializar este tipo personalizado nuevo, tenemos que sobreescribir _deserializeValue(), igual
que antes.
_deserializeValue(value, type) {
if (type == Date) {
return [Link](value);
if (type = TipoPersonalizado) {
return [Link](value);
Eso es todo de momento. Hemos hablado mucho de los types en las propiedades. Es un asunto importante,
pero hay muchas otras funcionalidades y posibilidades en las properties de Polymer que todavía no hemos
explicado y que veremos en los próximos artículos del manual de Polymer 2.
Existe un rico conjunto de funcionalidades en las propiedades de los componentes, incorporadas de casa en
Polymer 2, que nos servirán para satisfacer las más variadas necesidades en el desarrollo de custom elements
y de aplicaciones basadas en Web Components. Algunas de ellas ya las hemos visto anteriormente en el
Manual de Polymer 2. Por ejemplo, en el artículo anterior describimos con detalle cómo funciona la
declaración "type" en las propieades, aunque en este artículo las explicaremos todas, para que tengas una
visión global de las posibilidades.
Si vienes de Polymer 1.x, hay que decir que las propiedades de Polymer 2 tienen las mismas funcionalidades
que ya conoces, por lo que el presente artículo te servirá más bien para refrescar los conocimientos y
acceder a nuevos ejemplos de componentes realizados según las guías de Web Components V1 y Polymer
2.
Comenzamos con un listado de las funcionalidades configurables en las propiedades de los componentes.
type: Sirve para indicar el tipo (Boolean, Number, String, Array, Object y la menos conocida Date)
Sobre este asunto hemos hablado suficiente en el pasado artículo sobre los tipos y deserialización de
atributos.
value: Sirve para definir el valor predeterminado de una propiedad. Si se indica, se usará para
inicializar esa propiedad, aunque a la hora de usar el componente mandará la posible inicialización
que se realice en el atributo HTML del componente asociado con esa propiedad. El valor también
se puede calcular por medio de una función, que se invocará si es necesario en la inicialización del
componente. Se usará el valor de devolución de la función para inicializar la propiedad. Además,
para propiedades de tipo Array y Object, si queremos que exista una copia del array o el objeto para
cada elemento generado, tenemos que producir ese array u objeto en una función, devolviendo el
valor a inicializar con el correspondiente return.
reflectToAttribute: Sirve para que el valor de la propiedad actualice el valor de la propiedad en el
elemento. Por defecto esto no ocurre, por lo que, si se cambia el valor de una propiedad, no se
refleja en el HTML del componente host. Si queremos que se actualice el atributo en la etiqueta
De todas estas configuraciones de propiedades iremos realizando ejemplos a lo largo del manual. Muchas de
ellas las veremos en repetidas ocasiones y las analizaremos de manera más detallada en futuros artículos.
Aunque hemos visto anteriormente otros componentes en los cuales hemos incorporado propiedades,
vamos a ver un nuevo ejemplo de declaración de custom element Polymer 2 en el que tenemos un listado
amplio de propiedades.
En este caso tenemos un supuesto componente que muestra un contacto. El nombre es "persona-contacto"
y básicamente lo hemos hecho para tener varios tipos de propiedades con diversas inicializaciones. Es
interesante fijarse en la inicialización de las propiedades de tipo "Array" y tipo "Objetc", en las que tenemos
necesariamente que crear una función que hace el return del nuevo array de teléfonos de contacto o el
objeto con los datos complejos de su dirección.
<dom-module id="persona-contacto">
<template>
<style>
:host {
display: block
</style>
<h1>
</h1>
<p>
... dejamos como ejercicio crear un template adecuado con el que mostrar de manera ordenada sus propiedades
</p>
</template>
<script>
return 'persona-contacto';
return {
nombre: {
type: String,
value: ''
},
apellidos: {
type: String,
value: ''
},
direccion: {
type: Object,
value: function() {
return {
via: '',
numero: '',
codigoPostal: '',
ciudad: ''
};
},
telefonos: {
type: Array,
value: function() {
return [];
},
FechaNacimiento: Date,
email: {
type: String,
value: ''
},
identificadorGrupo: {
type: Number,
value: 0
},
activo: {
type: Boolean,
value: true
};
constructor() {
super();
[Link]([Link], PersonaContacto);
</script>
</dom-module>
Queda bastante por aprender de las propiedades, pero imagino que ya vamos teniendo las nociones básicas
para entender cómo funcionan. En los futuros artículos haremos nuevos ejemplo en los que trabajaremos
con otras configuraciones posibles para las propiedades.
En un pasado artículo del Manual de Polymer explicamos de manera general el listado de posibles
configuraciones en propiedades de Polymer 2. Vimos que hay muchas posibilidades y algunas de ellas
quedaron pendientes de una explicación más detallada.
Ahora vamos a practicar con una de las configuraciones de propiedad más simples, reflectToAttribute.
Como hemos mencionado anteriormente, sirve para que la etiqueta host refleje en sus atributos los cambios
que se produzcan en las propiedades del componente. Recuerda, el atributo es lo que escribimos en una
etiqueta HTML y la propiedad es lo que controlamos desde Javascript.
<componente-complejo></componente-complejo>
Al usarlo no indicamos ningún atributo, pero quizás dentro del componente complejo tenemos decenas de
propiedades para controlar su estado. Esas propiedades generalmente se gestionan dentro del componente y
los valores actuales no se reflejan modificando los atributos en la etiqueta host.
Nota: Por etiqueta host puedes entender la etiqueta que escribes para usar un componente. Dentro del
componente podemos tener un template con una serie de etiquetas en lo que llamamos el Shadow
DOM, pero el componente en si se usa con una sola etiqueta, con el nombre del elemento. Esa etiqueta
que se coloca en el HTML para usar el componente es la etiqueta host.
Entendido esto, podemos pasar a ver el uso de la configuración reflectToAttribute, que modifica el
comportamiento del componente para reflejar el estado de las propiedades que deseemos que se vean desde
fuera.
<dom-module id="uso-reflecttoattribute">
<template>
<style>
:host {
display: block
</style>
</template>
<script>
class TipoPersonalizado {
return 'uso-reflecttoattribute';
return {
reflejada: {
type: String,
reflectToAttribute: true
};
constructor() {
super();
cambiarValor() {
[Link] = [Link]();
[Link]([Link], UsoReflecttoattribute);
</script>
</dom-module>
<uso-reflecttoattribute></uso-reflecttoattribute>
Si lo ponemos en marcha, cuando el componente sufre el upgrade, veremos cómo en el instante aparece el
atributo "reflejada" con su valor predeterminado. Al inspeccionar el HTML con las herramientas de
desarrolladores veríamos esto (fíjate la etiqueta host "uso-reflecttoattribute"):
El componente además tiene un botón que, al pulsarse, cambia el estado de la propiedad, con lo que
veríamos que el nuevo estado también se refleja como atributo en la etiqueta host.
Es interesante saber que, cuando se refleja una propiedad en un atributo del componente ocurre una
serialización del valor de la propiedad. Esa serialización se realiza para convertir aquel valor de la propiedad
en un string, ya que los valores de los atributos en HTML solo aceptan cadenas.
Al serializar se realizan unas operaciones de conversión a cadena que dependen del tipo (type) de la
propiedad declarada. Por ejemplo un Date o Number se serializan con el método toString() o el array y
object se serializan con [Link](). Sin embargo nosotros podemos alterar la serialización
predeterminada o crear propiedades de tipos personalizados que tengan serializaciones determinadas por
nuestro propio código. Para ello tenemos que sobreescribir el método _serializeValue(value) en nuestro
componente.
_serializeValue(value) {
if (typeof(value) == 'number') {
return super._serializeValue(value);
En este ejemplo encontramos un primer condicional if que detecta si se debe serializar una propiedad de
tipo Number, en cuyo caso no se usará la serialización predeterminada de Polymer, sino una personalizada
por nosotros mimos. Como puedes apreciar, lo que se devuelve es una cadena con el número embutido
entre dos guiones.
En el segundo condicional se detecta si lo que se tiene que serializar es un objeto de la clase Cliente, en cuyo
caso se hace una serialización personalizada, en una cadena en la que se visualizan los datos del cliente.
Por último, para cualquier otro tipo de dato, se llama al método de serialización de la clase padre.
<dom-module id="uso-reflecttoattribute-serialize">
<template>
<style>
:host {
display: block
</style>
</template>
<script>
class Cliente {
[Link] = nombre;
[Link] = empresa;
[Link] = cif;
return 'uso-reflecttoattribute-serialize';
return {
numeroReflejado: {
type: Number,
value: 992,
reflectToAttribute: true
},
cliente: {
type: Cliente,
value: function() {
},
reflectToAttribute: true
};
constructor() {
super();
cambiarValor() {
[Link] = [Link]();
_serializeValue(value) {
if (typeof(value) == 'number') {
return super._serializeValue(value);
[Link]([Link], UsoReflecttoattributeSerialize);
</script>
</dom-module>
Las propiedades computadas son aquellas que toman su valor por medio de un cómputo, al que se llega
mediante los valores de otras propiedades del componente. Son especialmente útiles y se recurre a ellas en
gran cantidad de ocasiones.
Polymer 2, y su predecesor 1.x, hacen muy sencillo el trabajar con propiedades computadas. Además
realizan todo el trabajo pesado de manera automática, para que nosotros no tengamos que preocuparnos
porque los cómputos estén siempre correctamente actualizados. Para ello, cada vez que cambian los valores
de las propiedades usadas para realizar el cómputo, Polymer se encarga de actualizar el valor de las
propiedades computadas.
Se entenderán sin duda explicando casos de uso sencillos. Por ejemplo, podemos tener en un componente
dos propiedades: nombre y apellidos. Además podemos necesitar el "nombre completo" para otras cosas.
Esa propiedad nombre completo podría ser una propiedad computada, pues para obtener su valor se
consigue mediante la concatenación de las dos propiedades del elemento nombre y apellidos.
Otro ejemplo podría ser la letra del DNI. En España el número de identidad de las personas se acompaña a
una letra. Esa letra se puede calcular mediante unas operaciones a partir del número de identidad, por lo
tanto sería otro buen candidato a propiedad computada. Si tengo una factura y el total se calcula por medio
de la suma de todos los valores de cada concepto facturado, esa propiedad "total" podría ser una propiedad
computada. La fuerza de una clave, que se calcula en función de la cadena de la contraseña escrita por el
usuario, también podría ser una computada. Puedes encontrar interminables ejemplos.
Vamos a realizar un primer ejemplo de un custom element en el que incluimos una propiedad computada.
Vamos a trabajar sobre el sencillo caso del "nombre completo" que es la contactenación del nombre y
apellidos. Verás que trabajar con computed properties en Polymer 2 es algo realmente fácil.
nombre
apellidos
nombreCompleto, que es la computada a partir de las propiedades anteriores.
Para crear una propiedad computada tenemos que declararla como cualquier otra. Dentro de todas las
configuraciones que podemos realizar sobre las propiedades declaradas hay una llamada "computed". En
ella debemos de colocar el nombre del método que se usará para realizar el cálculo, a fin de hallar el valor de
la propiedad. Además hay que señalar qué propiedad o propiedades se usan para para calcular el valor
resultante.
En esta declaración de propiedades encontramos la sintaxis utilizada para definir las computadas:
return {
nombre: String,
apellidos: String,
nombreCompleto: {
type: String,
};
Como puedes ver, el método que se usará para realizar el cómputo es "definirNombreCompleto" y las
propiedades de las que se derivan el valor de la computada son "nombre" y "apellidos".
Esta declaración permite a Polymer 2 saber que, cada vez que cambien los valores de las propiedades
"nombre" y "apellidos", se tiene que invocar el método definirNombreCompleto().
Por supuesto, ese método lo tenemos que escribir dentro del componente. Recibirá exactamente los mismos
parámetros declarados en la propiedad computada y deberá devolver (return) el valor resultado del cálculo.
definirNombreCompleto(nombre, apellidos) {
Puedes ver a continuación el código del componente completo creado para este ejemplo.
<dom-module id="avatar-usuario">
<template>
<style>
:host {
display: block
</style>
<div>
[[nombreCompleto]]
</div>
</template>
<script>
return 'avatar-usuario';
return {
nombre: String,
apellidos: String,
nombreCompleto: {
type: String,
};
constructor() {
super();
definirNombreCompleto(nombre, apellidos) {
[Link]([Link], AvatarUsuario);
</script>
</dom-module>
Ahora veremos otro ejemplo de componente que tiene una propiedad computada, para calcular la fuerza de
una clave. Ya sabes, la fuerza depende de la clave escogida e indica al usuario que tan segura es una clave.
La declaración de nuestras propiedades ahora incluye la propia clave y su fuerza, siendo esta última una
computada.
return {
clave: {
type: String,
value: ''
},
fuerza: {
type: Number,
value: 0,
computed: 'calcularFuerzaClave(clave)'
};
El método que se usa para calcular la fuerza se llama calcularFuerzaClave() y recibe la clave por parámetro,
por tanto, cada vez que esa cadena "clave" cambie, se invocará al método que calcula la fuerza. Tendría un
código como el que sigue.
calcularFuerzaClave(clave) {
return 10;
return [Link];
Como puedes apreciar, un método de una computada devuelve siempre un valor por medio de return, valor
que se cargará en la propiedad en cuestión cada vez que uno de sus parámetros cambie.
Nota: aquí el algoritmo que calcula la seguridad de la clave es indiferente. Obviamente el que hemos
escogido no es muy bueno, pero es lo de menos para el objetivo de este artículo.
<dom-module id="fuerza-clave">
<template>
<style>
:host {
display: block
</style>
<div>
Fuerza: {{fuerza}}/10
</div>
</template>
<script>
return 'fuerza-clave';
return {
clave: {
type: String,
value: ''
},
fuerza: {
type: Number,
value: 0,
computed: 'calcularFuerzaClave(clave)'
};
constructor() {
super();
calcularFuerzaClave(clave) {
return 10;
return [Link];
[Link]([Link], FuerzaClave);
</script>
</dom-module>
Nota: en el código anterior observarás algo que probablemente te parecerá extraño: el binding del valor
de la clave escrito en un INPUT:
Esta sintaxis difiere de la que hemos explicado en el artículo de introducción al binding de Polymer 2 y
es necesaria cuando queremos hacer un doble binding sobre un elemento nativo del HTML y no un
elemento de Polymer. Hablaremos de este mecanismo de binding en otra ocasión.
En este artículo hemos podido conocer una de las herramientas más utilizadas en día a día del desarrollo de
componentes. Las propiedades computadas permiten reaccionar a cambios de una manera sencilla y
mantener actualizados los valores de los datos manejados internamente en el componente. Por medio de
una sintaxis sencilla podemos ejecutar funciones cuando ciertos valores cambian, ahorrando al desarrollador
la necesidad de suscribirse a eventos de cambio, lo que ahorra escribir código redundante al trabajar con
Polymer 2.
Hemos visto un par de ejemplos de componentes donde las propiedades computadas nos resultan de
utilidad, pero aplicaciones hay a montones y seguramente al desarrollar en poco tiempo encontrarás muchos
otros casos donde te resultan de utilidad.
Observers en Polymer 2
Una de las herramientas más importantes para el desarrollo de componentes de Polymer son los observers.
Los observers nos permiten estar informados de los cambios en los datos que maneja el componente, ya
sean propiedades comunes, propiedades de objetos, subpropiedades, o secciones de un array. Existen
observers sencillos y observers complejos y los vamos a conocer todos en los próximos artículos. Además
también conoceremos cambios en los observers en la nueva versión de la librería, Polymer 2, pues han
incorporado nuevas funcionalidades, como la declaración de comodines en los observers, para observar
varias cosas a la vez sin necesidad de especificarlas.
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.
A la hora de declarar una propiedad podemos configurar diversos comportamientos, que hemos descrito en
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) {
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.
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.
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
El primero de los componentes es el iron-icon, que es el que nos sirve para mostrar cualquier icono.
Una vez instalados tenemos que asegurarnos de hace los correspondientes imports.
<iron-icon icon="icons:check-circle">
Nota: iron-icons tiene como varias clasificaciones de iconos. La general la tienes en el import de iron-
[Link], pero luego tienes por ejemplo [Link] o [Link], 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.
return {
email: {
type: String,
observer: 'comprobarEmail'
},
icono: {
type: String,
value: "icons:clear"
};
comprobarEmail(email) {
if([Link](email)) {
[Link] = 'icons:check';
} else {
[Link] = '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.
<dom-module id="marcar-email-valido">
<template>
<style>
:host {
display: inline;
</style>
<iron-icon icon="[[icono]]">
</template>
<script>
return 'marcar-email-valido';
return {
email: {
type: String,
observer: 'comprobarEmail'
},
icono: {
type: String,
value: "icons:clear"
};
constructor() {
super();
comprobarEmail(email,e2, e3) {
if([Link](email)) {
[Link] = 'icons:check';
} else {
[Link] = '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 [Link](email);
[Link]([Link], MarcarEmailValido);
</script>
</dom-module>
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
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.
return {
valorSinImpuestos: {
type: Number,
observer: 'valorCambiado'
},
tipoIVA: {
type: Number
valorCambiado(nuevoValor, valorAnterior) {
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.
En el artículo anterior del Manual de Polymer 2 estudiamos los observers simples en Polymer 2, que nos
permiten observar los cambios en una propiedad. Pero en la práctica no siempre es suficiente este tipo de
observadores, puesto que muchos procesos dependen de más de una propiedad al mismo tiempo.
Para casos más avanzados en Polymer (tanto en la versión 1.x como en la 2.x) existen los observadores
complejos, que nos permiten declararlos de una manera diferente y más versátil, que vamos a estudiar en el
presente texto. Aunque ya vienen existiendo en Polymer 1.x, los observers complejos han sufrido cambios
interesantes que tendrán que aprender también los que ya conocían esta librería.
Declaración observers
Los observers complejos en Polymer requieren una declaración específica, independiente de la declaración
de propiedades. Esta declaración de observers complejos permite definir de manera centralizada todos los
observers utilizados en un componente, pero además habilita diversas posibilidades adicionales para los
métodos observadores.
Esta declaración es mucho más versátil, permitiendo observar cambios en una propiedad, pero también en
un conjunto de propiedades, en atributos profundos de un objeto o en las casillas de elementos de un array.
De manera similar a otras declaraciones en clases de componentes de Polymer 2, para la especificación de
los observers complejos usaremos también una propiedad estática, definida por un getter de la clase del
componente. No debería representar ningún problema, pues es algo que has hecho ya múltiples veces para
la definición de propiedades.
En este artículo vamos a conocer la sintaxis esencial de la declaración de observers, realizando ejemplos de
observar dos o más propiedades a la vez. En futuras entregas veremos otros ejemplos que nos permitirán
observar arrays o bien atributos internos de los objetos, a cualquier profundidad, por medio de caminos
(paths).
Los observers se indican por mediación de un array, en el que se declaran mediante una cadena todos los
métodos observadores y sus propiedades, un observer por posición del array.
return [
observer1(propiedad1, propiedad2)',
observerX(propiedadX)
En este caso hemos definido 3 observers, en los que se controla el valor de distintos juegos de propiedades.
Puedes observar al mismo tiempo cualquier número de propiedades del componente:
Nota: Incluso puedes observar una única propiedad, como en el método "observerX" (aunque en este
caso también podrías usar observers simples, siempre que esa propiedad no sea un array del que
queremos controlar sus ítem o una propiedad profunda de un objeto).
Para cada observador se declara el conjunto de propiedades que se están vigilando, por medio de los
parámetros del método observador. Luego se tienen que declarar los métodos que hacen la función de
observadores, por ejemplo:
observer1(propiedad1, propiedad2) {
Estos métodos se invocarán automáticamente cada vez que cambie una de las propiedades observadas.
Como te puedes imaginar, en el método observador recibimos los valores actuales de las propiedades. No
solo las propiedades que acaban de cambiar, sino el conjunto de propiedades observadas.
En el siguiente ejemplo podemos ver un componente en el que declaramos un observer complejo, por
medio de la declaración "observers". Nuestro ejemplo servirá para ilustrar el caso de vigilar el valor de dos
propiedades al mismo tiempo.
El componente es un supuesto elemento que nos ofrece un dato sobre la "probabilidad de lluvia". Para
calcular la probabilidad de lluvia hemos supuesto que necesitamos dos valores: la humedad y la presión
atmosférica. Como esa probabilidad cambia cuando cualquiera de esos dos valores se altere, tenemos que
usar la declaración de observers necesariamente.
Nota: No me he dedicado a estudiar meteorología, por lo que mis suposiciones para calcular la
probabilidad de lluvia son solo fruto de mi propia imaginación. En definitiva, nos da igual cómo se
calcule la probabilidad de lluvia, lo que nos interesa es ver un observer de dos propiedades a la vez.
Existen unos controles para cambiar los valores de presión atmosférica y de humedad, para que se pueda
comprobar que, cada vez que cambian, se invoca el método observador.
return [
'calcularProbabilidad(presionAtmosferica, humedad)'
calcularProbabilidad(presionAtmosferica, humedad) {
[Link] = 'Baja';
[Link] = 'Media';
[Link] = 'Alta';
} else {
El algoritmo usado es indiferente. Lo que tienes que ver es que el observador recibe los dos valores
observados y se usan dentro del método para calcular el dato que necesitábamos. Obviamente, dentro de
este método podríamos acceder a cualquier otra propiedad del componente, para obtener los valores
actuales o alterarlos.
<dom-module id="probabilidad-lluvia">
<template>
<style>
:host {
display: flex;
align-items: center;
font-family: sans-serif;
font-size: 0.9em;
padding: 10px;
justify-content: space-around;
span {
font-weight: bold;
display: block;
div {
text-align: center;
margin: 10px;
.probabilidad {
color: #003399;
font-size: 1.2em;
h2 {
font-size: 1.1em;
margin: 0 0 10px 0;
</style>
<div>
<span>Presión atmosférica</span>
[[presionAtmosferica]]
<footer>
<button on-click="aumentarPresion">+</button>
<button on-click="disminuirPresion">-</button>
</footer>
</div>
<div>
<span>Humedad</span>
[[humedad]]%
<footer>
<button on-click="aumentarHumedad">+</button>
<button on-click="disminuirHumedad">-</button>
</footer>
</div>
<div class="probabilidad">
<h2>Probabilidad de lluvia</h2>
[[probabilidadLluvia]]
</div>
</template>
<script>
return 'probabilidad-lluvia';
return {
presionAtmosferica: Number,
humedad: Number,
probabilidadLluvia: String
};
return [
'calcularProbabilidad(presionAtmosferica, humedad)'
constructor() {
super();
calcularProbabilidad(presionAtmosferica, humedad) {
[Link] = 'Baja';
[Link] = 'Media';
[Link] = 'Alta';
} else {
aumentarPresion() {
[Link]++;
aumentarHumedad() {
[Link] += 10;
disminuirPresion() {
[Link]--;
disminuirHumedad() {
[Link] -= 10;
[Link]([Link], ProbabilidadLluvia);
</script>
</dom-module>
Como práctica te sugerimos comprobar qué pasa a la hora de usar este componente, si hacemos algo como
esto:
Verás que, nada más inicializarse el componente, se llamará al método observador, con los valores de las
propiedades definidas.
Si por contra, no se definieran todas las propiedades, pero al menos una de ellas sí:
<probabilidad-lluvia presion-atmosferica="1.5"></probabilidad-lluvia>
Se invocaría también al método observador, con el valor definido en una propiedad y el valor "undefined"
en la otra.
Una limitación de los observers complejos, en relación a los observers simples sobre una única propiedad,
es que somos incapaces de recibir los valores anteriores de las propiedades.
Recuerda que en los observers simples, que se vigilaba únicamente una propiedad, recibimos como
parámetro tanto el valor actual de la propiedad como el valor anterior. Esto es algo que no permiten los
observers complejos, pues no hay manera de que nos provean de ese valor anterior.
Si estás haciendo un componente y para un caso concreto necesitas los valores actual y anterior de una
propiedad, la única solución automática sería apoyarse en observers simples.
Nota: Si necesitas observar dos propiedades al mismo tiempo y además conseguir sus valores
anteriores, el problema de usar observers sencillos serían las dependencias del observador. Pues
realmente solo estarías observando una propiedad y no dos al mismo tiempo. Recuerda que, por norma,
en los métodos de los observers deberíamos limitarnos a confiar solamente en las propiedades
observadas y no otras propiedades del componente. Por tanto, en un observador sencillo, necesario para
ser capaces de acceder al valor anterior, no deberíamos consultar otras propiedades. En el caso que lo
hagas deberías tener en cuenta:
Ahora ya sabes implementar observers complejos. Este conocimiento te servirá para innumerables
situaciones y junto con los observers simples que conociste en el artículo anterior ya tienes una buena
cantidad de información sobre cómo funcionan los observaores de Polymer 2, una de las fundamentales
herramientas que nos proporcionan para la implementación de componentes.
Sin embargo, aún nos queda por aprender algunos otros usos también bastante utilizados en observers,
como observar modificaciones de casillas de arrays y de propiedades profundas de objetos. En este apartado
Polymer 2 ofrece algunas importantes novedades, pero esto ya es un tema que dejamos para los próximos
capítulos del Manual de Polymer 2.
En el Manual de Polymer 2 hemos dedicado ya dos capítulos a los observers, pero aún no hemos terminado
de contarte todo. En el capítulo anterior vimos los observadores complejos de Polymer 2 y comprobamos
que mediante su declaración podemos vigilar el cambio en dos propiedades al mismo tiempo. En este
artículo debemos continuar hablando de ellos, pues hay detalles que no hemos explicado todavía y que
necesitarás saber para no volverte loco al usarlos en tus propios componentes.
Veremos cómo acompañar el estado en las propiedades profundas de los objetos y cómo permitir que
Polymer 2 ejecute los mecanismos de binding, y los observers, cuando se modifican mediante la asignación
de nuevos valores en el código de los métodos del componente.
Comencemos con una aclaración sobre las propiedades profundas de objetos (deep properties). Nosotros
podemos tener un objeto dentro de una propiedad de Polymer y ese objeto puede tener varias propiedades
profundas.
Por ejemplo, el objeto "contacto" puede tener propiedades profundas como "[Link]" y
"[Link]". El objeto "contacto" podría ser la propiedad de un componente y para distinguir a las
propiedades del objeto "nombre" o "email" las llamamos propiedades profundas.
El caso es que las propiedades profundas en Polymer, y esto es algo que ocurre tanto en 1.x como en 2.x,
tienen unos tratamientos especiales, para aumentar el rendimiento del framework, que son los que vamos a
explicar en este artículo.
Lo primero que debes saber es que, cuando realizamos un observador de un objeto, ese observador sólo se
ejecutará cuando todo el objeto cambie y no cuando cambie una de sus propiedades.
return {
contacto: {
type: Object,
observer: 'vigilarContacto'
Nota: Por si te lo preguntas, para que se ejecutase el observer vigilarContacto tendría que ocurrir que se
le asignase otro objeto a esa propiedad. Por ejemplo con [Link] = new('Miguel',
'miguel@[Link]'); Al crearse ese nuevo objeto y asignarse a la propiedad contacto, como el
objeto creado es totalmente nuevo, sí se se ejecutaría el método vigilarContacto. Sin embargo eso es algo
que generalmente no se desea, pues lo más normal es que el objeto siga siendo siempre el mismo y lo
que se altere en el uso del componente son sus propieades profundas.
Aunque usásemos observers complejos, si observamos el propio objeto, no estamos observando sus deep
properties.
return [
'observarObjeto(contacto)'
Esa era la sintaxis de los observers complejos, pero si lo que estamos observando es el objeto, no ocurriría
nada aunque cambiasen sus propiedades profundas.
Ahora que ha quedado claro que observar un objeto no funciona si lo que queremos es observar sus deep
properties, vamos a aprender qué se puede hacer.
Para comenzar, debes saber que las propiedades profundas solo se pueden observar desde observers
compejos. En ellos debemos declarar qué propiedades profundas del objeto se desean observar, indicando
el camino por medio del operador "punto".
return [
'comprobarNombre([Link])',
'comprobarVariosValores([Link], [Link])'
En la declaración anterior tienes dos ejemplos de observadores declarados, en el primero se vigila una
propiedad profunda única y en el segundo dos propiedades profundas al mismo tiempo.
Ya solamente te quedaría escribir el código de tus métodos observadores. Pero eso lo vamos a ver luego con
un ejemplo completo de componente.
Cuando se modifican propiedades profundas de los objetos Polymer 2 no siempre ejecuta los mecanismos
de binding y los observers declarados. Vamos a aprender ahora a asegurarnos que todo vaya correctamente
cuando nosotros alteramos las propiedades profundas en el código de los métodos del componente.
Nota: Esto lo hace Polymer por motivos de rendimiento, pues sería muy costoso escalar eventos ante la
modificación de cualquier propiedad profunda, ante cualquier situación en la que ésta pueda alterarse.
Ten en cuenta que un objeto puede tener decenas de propiedades y no todas te interesa que se estén
controlando mediante binding o observers, por lo que Polymer 2 obvia cualquier cambio en las
propiedades profundas a no ser que realmente se necesite.
Por ejemplo, vamos a continuar con nuestro ejemplo del objecto "contacto" y las propiedades profundas
"nombre" y "email". Si nosotros hacemos algo como esto:
metodoDelComponente() {
[Link] = 'miguel@[Link]';
No se ejecutaría el binding
<div>
Nombre: {{[Link]}}
</div>
return [
'comprobarNombre([Link])'
El método set(), que forma parte de cualquier componente de Polymer 2, es el que nosotros debemos usar
cuando se modifican propiedades profundas de los objetos. Es el que nos asegura que los mecanismos de
binding y los observers se ejecuten como se desea.
Para usar set() simplemente debemos indicar la cadena con el camino a la propiedad profunda que
queremos actualizar.
metodoDelComponente() {
Una vez asignado un nuevo valor a [Link] por medio de set(), comprobarás que los observadores
y los mecanismos de binding sí se ejecutan.
Ahora vamos a ver un componente que nos sirva de ejemplo para todo lo aprendido. En este componente
tenemos una interfaz para colocar una clave, que tiene dos campos de texto, uno para colocar la clave y otro
para repetirla (y así estar seguros que, siendo ambas claves iguales, la clave está bien escrita).
En nuestro componente tendremos la clave, y su repetición, declarada con un objeto, que tiene dos
propiedades profundas.
return {
clave: {
type: Object,
value: function() {
return {
value: '',
repeticion: ''
};
Nota: es relevante que indiques una inicialización para los valores de la clave, a la hora de usar esos
valores en campos de texto, aunque simplemente los campos desees que aparezcan vacíos. Los estamos
inicializando mediante una función, algo que es necesario en Polymer.
Uno para comprobar la clave y ver si está bien escrita, es segura y todo lo que necesites saber.
Otro para comprobar si las dos claves son iguales.
return [
'comprobarClave([Link])',
'comprobarIguales([Link], [Link])'
Ahora podemos ver los ejemplos de implementación de estos observadores, en los que se realizan tareas
básicas.
comprobarClave(cadena) {
if(cadena == '') {
} else {
[Link] = '';
comprobarIguales(value, repeticion) {
if(value != repeticion) {
} else {
[Link] = '';
Ahora imaginemos que tenemos un método que debe realizar un reseteo de los campos clave y la repetición
de la clave. Para ese reseteo debemos asignar nuevos valores a las propiedades profundas. Veamos este
código primero, aunque de momento esta alternativa "va con trampa" para reforzar algo sobre lo que ya
hemos hablado antes.
reset() {
[Link] = '';
[Link] = '';
¿Ese código de reset() funcionaría? Obviamente no, porque al modificar propiedades profundas debemos
usar set(), pues en caso contrario no se ejecutarían los observers ni los mecanismos de binding.
reset() {
[Link]('[Link]', '');
[Link]('[Link]', '');
Podemos ver el código completo del componente que hemos construido para ilustrar este artículo.
<dom-module id="claves-iguales">
<template>
<style>
:host {
display: block;
padding: 10px;
div {
display: flex;
align-items: center;
span {
color: red;
font-size: 0.9em;
paper-input {
width: 50%;
</style>
<div>
<span>[[errorClave]]</span>
</div>
<div>
<span>[[errorRepeticion]]</span>
</div>
<button on-click="reset">Resetear</button>
</template>
<script>
return 'claves-iguales';
return {
errorClave: String,
errorRepeticion: String,
clave: {
type: Object,
value: function() {
return {
value: '',
repeticion: ''
};
return [
'comprobarClave([Link])',
'comprobarIguales([Link], [Link])'
constructor() {
super();
comprobarClave(cadena) {
if(cadena == '') {
} else {
[Link] = '';
comprobarIguales(value, repeticion) {
if(value != repeticion) {
} else {
[Link] = '';
reset() {
// [Link] = '';
// [Link] = '';
[Link]('[Link]', '');
[Link]('[Link]', '');
[Link]([Link], ClavesIguales);
</script>
</dom-module>
Con esto ya hemos avanzado mucho en el campo de los observers, pero aún nos queda por aprender otros
casos importantes, como son la posibilidad de observar cambios en cualquier propiedad profunda, por
medio de un comodín, así como la realización de observadores en elementos de arrays. Más adelante lo
abordaremos.
Los observers en Polymer 2 tienen enormes posibilidades y nos permiten realizar todo tipo de acciones
cuando pasan cosas sobre las propiedades de los componentes. Hemos conocido en el Manual de Polymer
2 diversos tipos de observers y ahora le toca el turno a los que nos permiten estar al tanto sobre cambios en
los arrays.
Cada vez que se inserta una casilla en un array, o que se elimina, podemos ejecutar un método y tener
acceso a aquella información que ha cambiado. No son tan fáciles de manejar como otros observers ya
comentados, aviso, pero con un poco de práctica los podrás dominar sin mayores problemas. Esperamos
ayudarte a lo largo de este artículo.
Además de los propios observers, en el presente texto comenzaremos hablando de un tema también muy
importante, como son las funciones que existen en Polymer 2 para trabajar con los arrays, que se deben usar
para que las cosas funcionen como deben y se mantengan al día los mecanismos de binding.
El lenguaje Javascript contiene métodos específicos para realizar acciones sobre arrays, como añadir un
elemento en el array o eliminar una sección del array. Esos métodos nativos de Javascript funcionan en
Polymer 2 (y Polymer en general), ya que todo en el fondo es Javascript, pero tienen un problema: no
desencadenan los mecanismos de binding ni tampoco producen la ejecución de los observers.
Por ejemplo, imagina que tenemos una propiedad llamada "valores" en un componente. Esta propiedad que
contiene un array cualquiera. Dado que es un array Javascript, podríamos invocar al método nativo push()
sobre ese array.
En cambio, lo correcto es hacer uso del método push() propio de los componentes de Polymer 2, que
recibe el camino donde se encuentra el array y el valor que se quiere insertar allí.
Al final, el efecto sobre el array que tenemos en esa propiedad es el mismo, tanto con push() de Javascript
nativo, como con el método push() de Polymer. Pero la segunda opción será preferible, puesto que
producirá los comportamientos deseados en el componente.
Como podemos ver en la documentación de Polymer 2, existen varias funciones para mutación de arrays,
como pop(), push(), shift(), unshift() y splices().
Para declarar un observador que vigila las casillas del array debemos de usar la sintaxis especial de los
observers complejos. Aunque en vez de dar simplemente el nombre del array, vamos a indicar que
queremos vigilar sus casillas.
return [
'valorInsertadoEliminado([Link])'
Aquí estamos definiendo un observer sobre las casillas del array. El array está en la propiedad del
componente llamada "valores", pero no queremos observar el array como un todo, sino sus cambios en el
número de casillas, para lo que indicamos "[Link]" como parámetro del observador.
Nota: Este modo de observar arrays se presta a confusión. Para intentar solventar posibles dudas quiero
enumerar las cosas que podrías observar en un array:
1) Observar el array como un todo. Esto sería por ejemplo observar si la referencia a un array ha
cambiado. Es decir, si una variable que contenía un valor de array (recuerda que los arrays en las
variables se dan por referencia, siendo como un puntero a la posición de la memoria donde está el
array). En este caso tendrías que observar el array como cualquier otra propiedad de tipo numérico o
tipo cadena y el observer se ejecutará cuando la variable pasa a apuntar a otro array, es decir, cuando
cambia la referencia.
2) Observar la cantidad de casillas. Por ejemplo, detectar cuando un array se le agrega un elemento, o se
le retira un elemento, o varios. Esto lo harás con los observers que hemos relatado en este mismo
artículo. Y lo podrás conocer siempre que se usen las mencionadas funciones de manipulación de arrays.
3) Observar un cambio de valor en alguna casilla. Esta situación se podría dar si por ejemplo la casilla 1
cambia de un valor a otro. Es decir, si el valor de una casilla del array cambia. Para observar este tipo de
cambios, de manera granular en cada casilla del array, hay que usar otro tipo de observers que no hemos
visto todavía, que permiten mediante caminos y una especie de comodines, especificar que quieres
observar cualquier propiedad de un objeto o cualquier valor de casilla en un array. En el próximo
artículo de este manual es justamente de lo que vamos a hablar.
La potencia de Polymer 2 se encuentra a la hora de definir el método declarado como observador. Este
método se ejecutará ante cualquier cambio en las casillas del array, pero además Polymer le entregará como
parámetro un dato crucial, que es el detalle de la mutación que se ha producido.
Así pues, si se produjo alguna mutación, en el método observer vamos a recibir un parámetro. Ese
parámetro contendrá un objeto con una propiedad llamada "indexSplices". Splices podríamos traducirlo por
"empalmes", pero también lo podemos entender como las secciones del array que han sido transformadas.
Como pueden haberse producido transformaciones en varias casillas a la vez, dentro de indexSplices
encontraremos un array, con cada una de las transformaciones. Podemos verlo si simplemente volcamos a
la consola el contenido de los splices.
valorInsertadoEliminado(secciones) {
Cada uno de los empalmes que han mutado es a su vez un objeto, que nos ofrece todas las propiedades
necesarias para disponer de toda la información que nos permita entender qué es lo que cambió en el array.
Los datos de cada splice son estos:
Nota: Como decía, es verdad que puede parecer un poco complejo, pero lo interesante es que ahí
encontrarás todos los datos que necesitas para saber qué ha ocurrido con las casillas del array.
Obviamente no tienes por qué recibir valores representativos a todos esos datos en el objeto. Por
ejemplo, si se han insertado casillas no recibirás ninguna información en el array removed.
En este código podemos encontrar un ejemplo de uso del parámetro splices un poco más relevante.
valorInsertadoEliminado(secciones) {
[Link](explicaTransformacion);
Prueba a ver qué es lo que pasaría al insertar un valor, o insertar tres valores a la vez. Agregar un valor ya lo
hemos visto:
Agregar 3 valores al mismo tiempo se realiza simplemente indicando los otros valores como siguientes
parámetros de push.
Acabamos con un código de componente que no sirve para nada en concreto, solo para testear esta
<dom-module id="test-splices">
<template>
<style>
:host {
display: block
</style>
<br>
</template>
<script>
return 'test-splices';
return {
nuevoValor: String,
valores: {
type: Array,
};
return [
'valorInsertadoEliminado([Link])'
anadirValor() {
if(! isNaN(valor)) {
[Link]('valores', valor);
[Link]([Link]);
anadirTres() {
[Link]('valores', 1, 2, 3);
valorInsertadoEliminado(secciones) {
[Link](explicaTransformacion);
constructor() {
super();
[Link]([Link], TestSplices);
</script>
</dom-module>
Espero que a modo de demostración este componente te aclare algunas cosas sobre los observers en las
casillas de los arrays. El componente hace en realidad pocas cosas, pero suficiente para ir haciéndose una
idea sobre cómo operar con los datos que nos proporciona Polymer 2 para observar los arreglos. Lo cierto
es que Polymer te ofrece mucha información cuando se altera la cantidad de casillas del array, pero
dependiendo de tu programa y sus necesidades tendrás que usar algunos de esos datos, generalmente no
todos.
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 "[Link]", "[Link]".
En este caso, "matematicas" o "lengua" son las subpropiedades o propiedades profundas.
En un componente podemos tener una propiedad y a su vez esta propiedad tener subpropiedades.
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.
return [
'cambiosCalificacion(calificacion.*)'
];
Cada vez que cambie cualquiera de las calificaciones en este componente, se invocará al método
"cambiosCalificacion".
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.
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) {
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.
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.
<dom-module id="calificaciones-estudiante">
<template>
<style>
:host {
display: block
h1 {
margin: 5px 0;
font-size: 1.1em;
</style>
<h1>Matemáticas</h1>
<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>
<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>
<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>
</template>
<script>
return 'calificaciones-estudiante';
return {
media: Number,
aprobado: {
type: Boolean,
value: true
},
calificacion: {
type: Object,
value: function() {
return {
matematicas: 5,
lenguaje: 5,
ciencias: 5
};
};
return [
'cambiosCalificacion(calificacion.*)'
];
cambiosCalificacion(changeRecord) {
let suma = 0;
let materias = 0;
materias++;
suma += calificacionActual;
if(calificacionActual < 5) {
estaAprobado = false;
[Link] = estaAprobado;
constructor() {
super();
[Link]([Link], 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.
En los próximos artículos vamos a dar un repaso en profundidad a todo el sistema de data binding de
Polymer, aprendiendo muchos detalles importantes para entender y aprovechar bien las posibilidades de
esta librería para el desarrollo de web components.
Al principio del Manual de Polymer 2 hemos hablado de binding, pero nos hemos quedado en un
conocimiento muy general, que será necesario complementar con nuevos artículos. Así pues, nos vamos a
dedicar a analizar dos usos del binding muy habituales, como es el binding a propiedades y el binding a
atributos.
En realidad el binding a propiedades ya lo hemos usado en diversas ocasiones, por lo que no necesitará
muchas más explicaciones. Sin embargo el bindeo a atributos tiene algunos detalles importantes sobre los
que tenemos que hablar. Pero primero habría que aclarar estos dos conceptos, pues a priori pueden parecer
lo mismo.
Distinguir entre propiedades y atributos es fácil. Básicamente propiedades se refiere a elementos de Polymer
y sus propiedades declaradas, mientras que atributos se refiere a lo que son atributos estándar, de las
etiquetas HTML en general.
Lo que sabes de binding lo puedes aplicar a ambos casos, sin embargo hay algunas connotaciones que debes
saber.
En los componentes de Polymer tenemos propiedades, declaradas mediante la declaración "properties". Eso
son lo que llamamos propiedades del componente y son las que hemos usado repetidas veces a lo largo del
manual. Polymer facilita la asignación de valores a esas propiedades del componente, por medio del
template.
Polymer se encarga de asignar esos valores a las propiedades. En el caso de "label" simplemente le asigna
ese valor literal, pero en el caso del "value" tenemos un binding de propiedad. En este caso "value" es una
propiedad del elemento "paper-input", propia de Polymer.
Sin embargo, hay veces que quieres asignar valores a atributos de un elemento y no a propiedades
declaradas de Polymer. Es el caso de atributos como "class", "style", "hidden"... cualquier elemento del
HTML tiene esos atributos, por ejemplo una etiqueta DIV, P, etc. También hay etiquetas que tienen
atributos específicos de ellas mismas, por ejemplo la etiqueta A (un enlace) tiene el atributo "href". Incluso
elementos de Polymer pueden tener atributos estándar del HTML como class.
En general, como ves, todo lo que son atributos de las etiquetas, que vienen del lenguaje HTML, son lo que
llamamos atributos.
Como decía, el binding a propiedades de elementos de Polymer no tiene ningún secreto, y de hecho ya lo
hemos practicado mucho en el manual. Lo que tenemos que aprender es el binding de atributos, que es
prácticamente lo mismo, solo que tenemos que indicar un caracter "[[--body--]]quot; en el template.
Se debe colocar un $ justo después del nombre del atributo, por ejemplo:
Sólo se pueden hacer de una dirección (one way binding). Para hacer de dos direcciones hay
alternativas en algunos casos.
Internamente Polymer los implementa con el método nativo setAttribute()
En algunos navegadores puede que funcione el binding a atributos sin colocar el "[[--body--]]quot;,
pero no te confunda, pues en otros browsers puede no funcionar.
En el siguiente código puedes ver un ejemplo de componente en el que hemos colocado un binding a un
atributo. Se trata del atributo "href" de una etiqueta A, de enlace.
El componente del ejemplo es muy sencillo. El valor del href se recoge por medio de un campo de texto
implementado con el componente paper-input. Se asigna al atributo nativo del HTML mediante href$="
[[url]]".
<dom-module id="enlace-variable">
<template>
<style>
:host {
display: block
</style>
<a href$="[[url]]">[[url]]</a>
</template>
<script>
return 'enlace-variable';
return {
url: String
};
constructor() {
super();
[Link]([Link], EnlaceVariable);
</script>
</dom-module>
En realidad el bindeo sobre atributos es algo sencillo. Simplemente tienes que fijarte para no olvidar el
carácter "[[--body--]]quot; cuando estás bindeando al atributo, para asegurarte que funcione bien en todos
los navegadores.
Recuerda que el binding sobre atributos solo funciona en una dirección, por lo que no te va a servir si
quieres recibir un dato mediante doble binding, sobre un campo INPUT, por ejemplo. Para ese caso en
concreto, veremos nuevos mecanismos en un artículo más adelante.
En este artículo del Manual de Polymer 2 vamos a abordar de un tipo de propiedades directamente
vinculadas al bindeo de datos, la configuración notify, que permite que los componentes notifiquen cambios
a los padres mediante el sistema de binding.
Recuerda que el sistema de binding es un enlace, que permite especificar que cierto dato se compartirá por
diversos elementos o componentes, algo que se especifica en un template por medio de la notación [[]] o
{{}} (binding de una o de dos direcciones). Dijimos que los datos bindeados viajan siempre de los padres a
los hijos, hacia abajo. Pero además opcionalmente también podríamos conseguir que viajasen desde los
hijos hacia los padres, hacia arriba, usando el binding de dos direcciones.
No obstante, lo dicho anteriormente no siempre es posible. Es decir, para poder producirse el binding a dos
direcciones, el componente que pase los datos al padre debe de haberse construido para que lo permita,
usando la configuración notify en las propiedades necesarias.
Al declarar una propiedad, una de las cosas que podemos indicar es si esa propiedad notificará cambios a los
padres. Para ello usamos la configuración "notify".
return {
datoNotificable: {
type: String,
notify: true
},
};
La propiedad declarada "datoNotificable" se ha configurado con "notify" a true, por lo que ya está
preparada para enviar cambios a los padres que puedan usar este componente.
Ten en cuenta que, por defecto, las propiedades de los componentes tienen el valor notify a false, por lo que
no escalarían los cambios hacia los padres de manera predeterminada.
Notify es una configuración que no se debería usar, a no ser que realmente sea necesaria. Si el componente
no necesita comunicarse con el padre para nada, entonces no tiene sentido usar notify. Solo en el caso que
el componente necesite comunicar con el padre tendría sentido configurar alguna de sus propiedades con
notify a true, pero aún así no siempre será la mejor opción.
Imagina un componente que es un banner por el que rotan varias imágenes, o unos textos que parpadean
alternando el mensaje que se visualiza. Ese banner lo colocas en la página, para adornar y quizás no necesita
decirle nada al padre. Va cambiando y punto. Si se hace clic sobre el banner se podría accionar un enlace,
usando una simple etiqueta A, pero no se necesita avisar al padre de nada en concreto. Entonces,
obviamente, no tendrá sentido usar notify.
Ahora piensa en un componente algo diferente, por ejemplo un campo de formulario para escribir una
clave, que te indica la fortaleza de esa clave. Ese campo de formulario lo puedes hacer mediante un
componente que podría llamarse "clave-segura". Al usar ese componente comúnmente se colocará dentro
de un formulario, que puede tener otra serie de campos creando una jerarquía de elementos como podría
ser la que tienes en la imagen:
En este esquema tenemos un componente, que es un formulario de contacto completo. Ese componente
tiene a su vez varios hijos, que podrían ser campos INPUT (el elemento caja de texto nativo del HTML),
campos paper-input (una caja de texto con el estilo de Material Design) o aquel componente que nos
referíamos antes que permite escribir una clave y te informa sobre su fortaleza.
Todos los componentes hijos de formulario-contacto tiene sentido que comuniquen el estado a su padre.
En el componente formulario-contacto nos interesa saber qué datos se han escrito en todos los campos de
formulario y por ello necesita que los componentes hijos notifiquen a los padres la propiedad donde se
almacene aquello que hay escrito. Para ello:
El campo INPUT, por ser nativo del HTML y no un componente de Polymer, tiene un tratamiento
del binding que es un poco más avanzado y que veremos en otro artículo.
El campo paper-input está ya construido y forma parte del catálogo de componentes del equipo de
Polymer. En este caso el componente ya está construido para que permita un doble binding. Lo
puedes comprobar si te fijas en la documentación del elemento, en su propiedad "value", donde
indican que es una propiedad configurada con notify.
El campo clave-segura, que es un campo que bien podríamos haber desarrollado nosotros, necesitaremos
crearlo de modo que el valor de la clave sea notificado al padre. Para ello tendremos que configurar la
propiedad en cuestión con el valor notify a true.
return {
clave: {
type: String,
value: '',
notify: true
},
fuerza: {
type: Number,
value: 0,
computed: 'calcularFuerzaClave(clave)'
};
Esta parte ya la hemos realizado en otras ocasiones por lo que no habrá dudas. Sin embargo, para poder
tener el binding a dos direcciones necesitamos especificarlo en el template:
<clave-segura clave="{{clave}}"></clave-segura>
1. Si en el template colocamos bindeo con los corchetes, por ejemplo clave="[[clave]]", aunque el
componente tenga la propiedad declarada como notify, no se producirá el envío del dato del hijo al
padre, puesto que la configuración con los corchetes manda, indicando binding de una sola
dirección.
2. Si nuestro componente no tiene una propiedad configurada como notify, por mucho que en el
template coloquemos el bindeo con las dos llaves {{}}, el enlace no será de dos direcciones. Como
hemos señalado, no habrá envío de datos del hijo al padre si el componente no está preparado para
ello con la configuración notify.
Como ya es costumbre, finalizamos el artículo con un nuevo ejemplo de componente realizado con
Polymer 2, en el que mostramos cómo configurar con notify la propiedad necesaria.
Este componente serviría para hacer una valoración de cualquier cosa. El componente crea una lista de
posibles valoraciones, que podemos seleccionar haciendo clic. Dependiendo de la valoración seleccionada,
notifica hacia el padre el valor pertinente.
<dom-module id="dw-valorar">
<template>
<style>
:host {
display: block
ul {
margin: 0;
padding: 0;
li {
list-style: none;
margin-bottom: 5px;
.marcado {
font-size: 110%;
font-weight: bold;
color: orange;
.desmarcado {
color: #999;
</style>
<ul>
</ul>
</template>
<script>
return 'dw-valorar';
return {
valoracion: {
type: String,
notify: true,
observer: 'valoracionObserver'
},
buenoClass: {
type: String,
value: 'desmarcado'
},
regularClass: {
type: String,
value: 'desmarcado'
},
maloClass: {
type: String,
value: 'desmarcado'
};
constructor() {
super();
valoracionObserver(valoracion) {
[Link]('valoracionObserver', valoracion);
marcarBueno() {
[Link] = 'bueno';
marcarRegular() {
[Link] = 'regular';
marcarMalo() {
[Link] = 'malo';
[Link]([Link], DwValorar);
</script>
</dom-module>
Para la realización del componente hemos usado simples textos, en los que es posible hacer clic para marcar
una valoración. Cada vez que se hace clic en una de las posibles valoraciones se invoca un evento para
marcar la correspondiente valoración en la propiedad configurada como notify.
Cada vez que cambia la valoración, por medio de un observer, se ejecuta el método valoracionObserver(),
que se encarga de adaptar el estilo de la opción marcada, para resaltarlo sobre los demás.
Para mostrar visualmente cuál es la valoración seleccionada hemos usado una clase de CSS. Para saber qué
clase mostrar en cada componente he usado una propiedad y bindeado esa propiedad al atributo class.
Nota: el bindeo al atributo class lo hemos visto en el artículo de bindeo a propiedades vs bindeo a
atributos.
El componente es autónomo, lo podrías usar en cualquier otro componente donde lo necesites, y mediante
un binding de dos direcciones podrás estar al tanto de la valoración que se haya marcado.
<dw-valorar valoracion="{{resultadoValoracion}}"></dw-valorar>
Estéticamente este componente no es nada espectacular, pero hace su función. Pone con una clase CSS (un
estilo) especial el elemento de valoración que se ha marcado, para representarlo visualmente. Quedaría más
o menos como se puede ver en la imagen siguiente:
Seguro que tú puedes mejorar este componente, por ejemplo representando los mensajes de valoración con
iconos de caritas sonrientes o tristes. Al usar el componente podrás utilizar el valor de la valoración, que
recibimos por el binding de dos direcciones.
<dw-valorar valoracion="{{valoracion}}"></dw-valorar>
De momento eso es todo. Como práctica te sugerimos eliminar la configuración "notify" sobre la propiedad
"valoracion", para comprobar cómo el padre no se enterará de los cambios, ya que no serían así notificados.
En este artículo del Manual de Polymer 2 vamos a tratar diversos asuntos relacionados con el data-binding
en la librería. Son prácticas habituales que reunimos en un solo artículo porque resultan fáciles de entender y
de explicar. Las usarás bastante para desarrollar todo tipo de componentes.
Veremos los operadores de negación en los bindeos, así como los bindings computados y los bindings
compuestos, con ejemplos de componentes, que nos permitirán seguir practicando en el desarrollo de
custom elements con Polymer 2. Pero recuerda que ya venimos de largo hablando sobre binding en
artículos anteriores, por lo que, si necesitas aclarar conceptos, te recomendamos comenzar por el artículo
Binding en Polymer 2.
Este es un operador que podemos insertar en los bindings. Se usa el mismo carácter de negación de
Javascript y sirve lógicamente para lo mismo que cualquier negación programática. El valor bindeado será la
negación de aquella propiedad que se está usando.
[[! propiedad]]
Lo puedes usar siempre que lo necesites, pero un caso habitual es cuando tienes que mostrar unas veces un
contenido y otras veces otro. En este caso mantienes una única propiedad y la usas dos veces, una de ellas
negada. Ese ejemplo es el que hemos usado en el componente a continuación, que muestra un icono u otro
en función de algo estar encendido o apagado.
<dom-module id="encendido-apagado">
<template>
<style>
:host {
display: block
</style>
</template>
<script>
return 'encendido-apagado';
return {
encendido: {
type: Boolean,
value: false
};
constructor() {
super();
[Link]([Link], EncendidoApagado);
</script>
</dom-module>
La clave de este componente está en los atributos hidden, que ocultan o muestran algo.
El icono que indica apagado se tiene que ocultar cuando está encendido. El icono de encendido se tiene que
ocultar cuando NO está encencido.
Nota: Puedes ver que estamos realizando un binding a atributo, para el atributo hidden, porque se
utiliza el carácter "[[--body--]]quot;. Si no lo conoces te recomendamos la lectura del artículo bindeo a
propiedades vs bindeo a atributos.
<encendido-apagado></encendido-apagado>
<encendido-apagado encendido></encendido-apagado>
En uno de los casos, el primero, aparecerá el icono como apagado. En el segundo caso aparecerá como
encendido.
Hasta ahora hemos usado siempre binding por medio de propiedades del componente, indicando entre
dobles corchetes o dobles llaves el nombre de la propiedad que se debe bindear en algún lugar. Sin embargo
existe otra posibilidad muy útil del binding en Polymer 2 que consiste en usar funciones que nos devuelven
el valor que debe bindearse. El nombre de esta utilidad es bindings computados, o computed bindings.
Las funciones son capaces de realizar un cálculo y mediante su return indicaremos aquello que debe bindear.
Como te imaginaras, estas funciones se implementan por medio de métodos en la clase del componente.
Nota: podrías entender los computed bindings como un tipo especial de propiedades computadas,
explicadas anteriormente. Solo que las propiedades computadas había que declararlas en las properties
del elemento y los computed bindings se declaran en el template al definir el correspondiente binding.
<div>
</div>
El método "_diasFestivosMes" recibe un mes (se supone que mes es una propiedad del componente). Usará
ese mes para calcular los días festivos y devolverá el resultado que se deba mostrar. Cada vez que el valor de
la propiedad "mes" cambie en el componente, el método "_diasFestivosMes" se volverá a invocar,
devolviendo siempre el resultado actualizado, que a su vez provocará que se refresque el template con el
nuevo valor calculado.
Vamos a ver a continuación un componente sencillo que hace uso de los bindings computados.
Básicamente el componente aplica el IVA a un valor, para lo que necesita conocerse el valor sin impuestos y
el tipo de IVA a aplicar. El propio valor más el IVA se calcula en función de los otros datos, por lo que
podríamos hacer un binding computado para visualizarlo en el template del componente.
<dom-module id="aplicador-iva">
<template>
<style>
:host {
display: block
</style>
<div>
<span>Valor:</span> {{valor}}
</div>
<div>
</div>
<div>
</div>
</template>
<script>
return 'aplicador-iva';
return {
valor: {
type: Number,
value: 0
},
tipoIva: {
type: Number,
value: 21
};
constructor() {
super();
_valorConIva(valor, tipoIva) {
[Link]([Link], AplicadorIva);
</script>
</dom-module>
El método "_valorConIva" se encarga de devolver el valor que debe mostrarse como consecuencia del
computed binding. A ese método se le envían los valores "valor" y "tipoIva", que son propiedades del
componente. Cada vez que las propiedades "valor" y "tipoIva" cambian se invoca de nuevo el método.
Para poder usar este componente tendrás que enviar los valores de las propiedades asignando atributos en el
HTML:
<aplicador-iva valor="100"></aplicador-iva>
Las dependencias del bindeo computado, que son los datos que necesita para calcular el valor a
mostrar, serán derivados del scope (ámbito) actual. Esto quiere decir que dentro de un template de
repetición (dom-repeat), el scope puede incluir la propiedad "item", que no es una propiedad del
propio componente.
Puedes enviar literales de cualquier tipo a los métodos del binding computado.
Si un computed binding no tiene ningún parámetro ocurrirá que la invocación al método se realizará
una única vez.
Otra alternativa para utilizar en data binding son los bindings compuestos, que permiten concatenar una
cadena con el valor de una propiedad.
Es algo muy útil, cuando la propiedad es la parte del valor que se desea bindear. Por ejemplo la URL de una
imagen.
En este caso el $ del src es necesario de usar porque estamos bindeando a un atributo, pero podrías bindear
a una propiedad de componentes Polymer perfectamente.
<avatar-usuario imagen="[Link]
Cada vez que cambie la propiedad "identificadorUsuario" cambiará el valor del binding compuesto. Además
tienes que saber que en los bindings compuestos solo se puede hacer binding de 1 dirección, de modo que,
aun usando dobles llaves el binding sería solo de padres hacia hijos.
Ten en cuenta que una propiedad con valor "undefined" será bindeada como una cadena vacía. Lo puedes
ver usando un componente como el del siguiente ejemplo.
<dom-module id="enlace-perfil">
<template>
<style>
:host {
display: block
</style>
</template>
<script>
return 'enlace-perfil';
return {
idUsuario: String
};
constructor() {
super();
[Link]([Link], EnlacePerfil);
</script>
</dom-module>
La propiedad "idUsuario" no tiene un valor predeterminado, por lo que se crea con el valor "undefined". Si
usas este componente sin indicar un identificador de usuario:
<enlace-perfil></enlace-perfil>
<enlace-perfil id-usuario="44"></enlace-perfil>
El binding en Polymer es muy amplio, con numerosas alternativas y utilidades que hemos visto ya a lo largo
de diversos artículos del Manual de Polymer 2. Sin embargo, debemos avisar que todos los
comportamientos que hemos tratado se refieren fundamentalmente a custom elements creados por medio
de la librería Polymer. Para los elementos nativos de HTML, o cualquier otro elemento que no haya sido
creado mediante Polymer, hay algunos detalles que cambian.
No es cuestión de asustarse, puesto que todo lo aprendido sirve también, solo que hay que aplicar una
sintaxis especial al definir el binding en el template, que vamos a explicar en el presente artículo.
Elementos Polymer: son todos los custom elements que han sido definidos con la librería Polymer.
Elementos "Non-Polymer": son todos los elementos nativos del HTML y custom elements que no
hayan sido creados con Polymer específicamente, como los generados únicamente con el estándar
Web Components.
Solo a título informativo, los elementos de Polymer hacen internamente y de manera transparente para el
desarrollador una notificación de eventos de cambio, que usa la librería para controlar cuándo las
propiedades se alteran y poner en marcha los mecanismos de binding a dos direcciones.
Sin embargo, elementos nativos del HTML y Non-Polymer en general no realizan esas notificaciones de
cambios, por lo que es imposible que Polymer realice los mecanismos de binding a dos direcciones.
Para que Polymer ponga en marcha el binding en dos direcciones, sin necesidad de las notificaciones de
eventos ante cambios, hay que especificar en el template el binding con una sintaxis especial.
Simplemente se coloca, entre las dos llaves que definen el "two way binding", el nombre de la propiedad
bindeada, seguido de "::" y el nombre del evento que debe activar el binding.
Por ejemplo, si queremos estar atentos a los cambios sobre un campo input, aplicando doble binding en una
propiedad, debemos escribir algo como esto:
<input value="{{propiedadBindeada::input}}">
En este campo input tenemos bindeado el value (lo que hay escrito) a la "propiedadBindeada". Pero el
binding en dos direcciones sólo se producirá ante el evento "input" del campo input. Parece un poco raro,
pero en realidad es sencillo.
El evento input de un campo de formulario se dispara cuando cambia el value de ese campo. Por lo tanto,
ante cambios en el value del campo input, se producirá el binding en dos direcciones, que es lo que
finalmente estábamos necesitando.
El evento "input" también lo dispara por ejemplo un campo "select". De modo que podríamos usar esta
sintaxis para bindear a un menú desplegable de formulario.
<select value="{{propiedadBindeada::input}}">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
Esto equivaldría a tener bindeado el evento change sobre este campo select, ya que se dispara en similares
condiciones.
<select value="{{propiedadBindeada::change}}">
Podemos usar perfectamente otros eventos de elementos HTML en general, por ejemplo:
Este binding sólo se producirá en el momento que se libera la tecla, durante la escritura en el campo input.
Elementos como un checkbox no disparan el evento input, porque no cambia el value nunca del elemento.
En este caso lo lógico sería usar el evento change.
El ejemplo que vamos a ver ahora es una sencilla calculadora creada en un Web Component Polymer. Usa
varios tipos de elementos de formulario para que podamos ver cómo se implementa el doble binding sobre
ellos.
<dom-module id="calculadora-polymer">
<template>
<style>
:host {
display: block
</style>
<div>
</div>
<div>
<span>Operación:</span>
<select value="{{operacion::input}}">
<option value="+">+</option>
<option value="-">-</option>
<option value="*">*</option>
<option value="/">/</option>
</select>
</div>
<div>
</div>
<div>
</div>
<div>
</div>
</template>
<script>
return 'calculadora-polymer';
return {
op1: {
type: Number,
value: 0
},
op2: {
type: Number,
value: 0
},
operacion: {
type: String,
value: '+'
},
redondear: {
type: Boolean,
value: false
};
constructor() {
super();
op1 = parseFloat(op1);
op2 = parseFloat(op2);
if(isNaN(op1) || isNaN(op2)) {
redondeoCondicional(valor, redondear) {
if(!redondear) {
return valor;
} else {
[Link]([Link], CalculadoraPolymer);
</script>
</dom-module>
Es importante sobre todo fijarse en el template de este componente, en el que ves varios elementos de
Para usar este elemento, una vez importado, colocamos simplemente esta etiqueta.
<calculadora-polymer></calculadora-polymer>
Con esto hemos visto la mayoría de las cosas relacionadas con el Binding que debemos de conocer sobre la
librería Polymer 2. Ahora es tiempo de pasar a otro tema relevante para seguir aprendiendo a desarrollar
nuestros propios componentes.
A continuación otros artículos con asuntos que pueden resultar destacables en el mundo de Polymer y los
Web Components.
Lit-HTML
Qué es Lit-HTML y cómo usar esta librería para creación de templates HTML en Javascript,
capaz de ofrecer un altísimo rendimiento.
Polymer 3.0 está a punto de salir. En el momento de escribir estas líneas ya han presentado varias "Preview
Releases" y quizás cuando leas este texto ya dispongamos de una primera release final. Seguro que como yo
hay muchos otros desarrolladores ansiosos por comenzar a trabajar con esta librería basada en Web
Components, así que para calentar motores hoy vamos a hablar de Lit-HTML.
Lit-HTML es una librería Javascript creada por el equipo de Polymer. Sin embargo, es una librería
autónoma, que se puede usar en cualquier sitio, incluso fuera del marco del desarrollo con Polymer.
La librería se ha desarrollado para satisfacer las demandas de un sistema de templates HTML, capaz de
aportar un gran rendimiento, funcionando del lado de Javascript con ES6 Modules. Estamos actualmente en
una fase temprana de desarrollo, pero ya resulta muy prometedora, por diversos motivos que vamos a
explicar en este artículo.
Si sigues leyendo te voy a ofrecer una introducción a Lit-HTML, una comparación con VDOM y
finalmente una descripción de los primeros pasos para usar Lit-HTML, directamente con Javascript, sin usar
Polymer.
Para entender el nacimiento de Lit-HTML tenemos que mirar hacia Polymer 3.0. La novedad principal con
respecto a Polymer 2 es el cambio de los HTML Imports a los ES6 imports. Este detalle hace que varias
cosas hayan cambiado en Polymer, ya que ahora el marco de desarrollo de un componente no es un archivo
HTML, sino un archivo Javascript.
Por tanto, había que buscar una manera amistosa con el desarrollador, además de potente y versátil, para
conseguir definir templates a partir de código Javascript. Otras librerías habían hecho un gran trabajo en
este sentido, como ReactJS con su "Virtual DOM" (VDOM), pero todavía había margen de mejora.
En las primeras previews de Polymer 3 se escribía el template con un ES6 Template String. Pero esa
primera aproximación no aportaba la flexibilidad y rendimiento que el equipo de Polymer deseaba, por lo
que idearon Lit-HTML, un nuevo sistema de templates HTML, amistoso, ligero y potente, que se definen
desde Javascript.
Claves de Lit-HTML
Los sistemas de templates se crean para ofrecer al desarrollador una manera de escribir código de las vistas,
en HTML, de manera que sea fácilmente mantenible. Además, para establecer un puente entre los datos que
están en Javascript y la presentación que está en HTML.
Sin embargo, los requisitos a satisfacer para un sistema de templates son a veces difíciles de combinar. Si
queremos ofrecer una amigable experiencia de desarrollo muchas veces irá en detrimento del rendimiento
del sistema de templates. Es decir, a menudo ocurre que un código ideal para el desarrollador, se traduce en
la necesidad de mayor procesamiento para Javascript. Encontrar un compromiso entre estas dos partes es lo
que se ha procurado al crear Lit-HTML.
Los objetivos sobre los que se ha construido Lit-HTML, son los siguientes:
Además, debido al cambio relatado de los templates de Polymer, que han pasado de HTML al lado de
Javascript, se necesitaba cubrir esos requisitos del lado de Javascript y no del lado del HTML como
teníamos anteriormente.
La librería desarrollada para cubrir esta necesidad ha conseguido aunar de la mejor manera posible los
requisitos iniciales.
Permite escribir HTML dentro de código Javascript, de una manera amistosa para el desarrollador.
A la vez, es mucho más poderosa que un simple template string de ES6.
Genera rápido los templates, básicamente porque aprovecha en su mayor parte código nativo de
Javascript ES6 y dispone además de un sistema de cacheo de templates. Gracias a ese sistema de
cacheo, si se usa el mismo template varias veces (algo bastante común), el proceso de generación del
template reaprovecha el procesamiento anterior.
Se actualiza muy rápido cuando hay datos que cambian dentro del template, incluso más rápido que
el VDOM de React (Luego haremos una pequeña comparación de ambos sistemas)
Es muy pequeño. Ocupa menos de 2KB.
Es fácil de usar y extensible para los desarrolladores, que pueden crear nueva funcionalidad encima
de la librería para satisfacer futura demanda.
Una de las librerías para trabajo con vistas más representativas hasta el momento es la implementada en
ReactJS, llamada VDOM o "Virtual Dom". Desde que apareció ha cautivado a la comunidad debido a su
alto rendimiento a la hora de manipular el DOM de la página.
El modo de funcionamiento de VDOM es de sobra conocido: Existe un DOM virtual creado en memoria.
Cada vez que los datos asociados a un template cambia, se realiza una actualización del DOM virtual, que se
produce extraordinariamente rápido. Luego, se compara el DOM virtual contra el DOM real del navegador
y se actualiza simplemente aquellos nodos que han sufrido transformaciones. Actualizar el DOM del
navegador es más costoso en tiempo de procesamiento, por lo que el VDOM consigue optimizar las
actualizaciones en el template gracias a que realmente sólo toca el DOM real en los lugares donde es
estrictamente necesario.
1. En un template existen partes estáticas y partes dinámicas. Las partes estáticas no se actualizan
nunca y las dinámicas, que básicamente son los datos que tienes que volcar en el template, sí pueden
recibir cambios. Es inútil comprobar cambios en todo el DOM del template, cuando realmente los
cambios sólo se pueden llegar a producir en determinados y concretos puntos.
2. Habría que ver hasta qué punto es realmente necesario mantener una copia del DOM, en un
"Virtual DOM". Si se consigue realizar el mismo trabajo sin esa copia, estaremos rebajando la
cantidad de memoria y procesamiento.
Ahí es donde Lit-HTML ha podido optimizar VDOM. No crea un DOM virtual y separa el template en sus
partes estáticas y las partes dinámicas. Cuando surgen cambios en los datos solamente necesita comprobar
las partes dinámicas del template, actualizando aquellos nodos que realmente son necesarios.
En resumen, mientras que en VDOM el coste de procesamiento (la complejidad del algoritmo) crece a
medida que aumenta el número de nodos de un template, en Lit-HTML crece solamente en función de los
puntos del template donde se están interpolando valores. Aparte de ocupar menos espacio en KB que
VDOM y gastar menos memoria, en la mayoría de los casos, el algoritmo de Lit-HTML es más optimizado,
porque tiene que hacer un menor número de comprobaciones por template actualizado.
Nota: El equipo de Google ha publicado un benchmark en el que comparan el motor de plantillas Lit-
HTML contra el implementado con VDOM de React, incluso el de Preact y el de Inferno, Angular, etc.
En las métricas obtenidas hasta el momento Lit-HTML ha resultado ganador.
La imagen que vemos a continuación trata de explicar este punto de la complejidad algorítmica de varios
sistemas de trabajo con vistas (está extraída de una charla sobre Lit-HTML de Justin Fagnani
@justinfagnani en el Chrome Dev Submmit 2017):
En el diagrama anterior tenemos los nodos estáticos marcados con color azul y los dinámicos en verde,
donde realmente hay cosas que pueden cambiar a medida que los datos del template se actualizan. Sin
embargo, a la hora de producirse cambios en un template, no todos los datos cambian, por lo que es posible
que no todos los puntos dinámicos se tengan que actualizar verdaderamente. La "A" representada en este
diagrama indica que este nodo de datos es el que ha cambiado.
VDOM: donde el coste por actualización del template (complejidad del algoritmo) depende del
número de nodos del template, tanto los estáticos como los dinámicos.
Lit-HTML: donde el coste de actualización del template depende del número de nodos dinámicos.
Polymer: donde el coste de actualización depende del número de nodos que han cambiado.
Nota: A la vista de la anterior imagen, de quedarnos con algún algoritmo, quizás perferiríamos el de
Polymer. Pero realmente hay que tener en cuenta dos parámetros para el rendimiento: 1) El coste por
número de nodos y 2) El coste que te lleva cada nodo. ReactJS, Preact o Inferno con VDOM son muy
buenos porque han podido optimizar el coste por nodo, por procesarse en memoria, pero sin embargo
están obligados a comparar un gran número de nodos. Polymer tiene un coste por nodo alto, pero en
cambio realiza menos actualizaciones de nodos, ya que solo comprueba los nodos que realmente se han
actualizados. La ventaja de Lit-HTML es combinar lo mejor de ambas alternativas. Además, usa
comportamientos nativos de Javascript en su mayor medida y gracias a ello su peso es menor y la
velocidad es mucho mayor. Lit-HTML escala en función de los puntos dinámicos del template, que
podrían ser menos puntos que los que han cambiado verdaderamente, pero al producirse el
procesamiento más rápido, consigue mayorb rendimiento que los otros.
Como viene siendo costumbre en Polymer, los ingenieros de Google han producido un algoritmo que usa
las capacidades existentes de manera nativa en los navegadores. Lo resumimos a continuación, aunque de
todo este proceso no nos tenemos que preocupar como desarrolladores, ya que es algo que realiza Lit-
HTML por debajo. Sin embargo, sí que es interesante de conocer para que podamos entender mejor cómo
se usará este nueva librería.
A la hora de construir Lit-HTML se han basado en la etiqueta template existente en todos los navegadores
de la actualidad.
Lo que hacen es construir una etiqueta template, un template HTML nativo. En ese template memorizan
los huecos donde se insertan los valores que vienen desde Javascript. Al cambiar los datos que se vuelcan al
template, saben en qué puntos deben colocarlos, con lo que las operaciones se realizan muy rápido.
Además, el coste de creación de un template Lit-HTML se realiza una vez por template. Si más adelante se
usa ese template, porque se repita un determinado bloque varias veces, se reutiliza el template tag generado
la primera vez.
Son de sobra conocidos los Template Strings de Javascript ES6. Sin embargo Lit-HTML va un paso más
allá para sacar partido de los "Tagged Template String Literals".
Son simplemente literales de string en los que tenemos varias capacidades extra:
html`
<div>
<h1>${titulo}</h1>
<p>${descripcion}</p>
</div>
La palabra "html" del inicio es el tag (la etiqueta), una función que procesará ese literal de cadena y nos
devolverá el objeto template.
Usar Lit-HTML
Ahora vamos a dar unas primeras notas sobre cómo poner las manos encima de Lit-HTML y comenzar a
usarlo en el marco de Javascript, sin aplicar ningún framework.
Luego podremos usar la librería. Pero ten en cuenta que está desarrollada en ES6, por lo que tendremos que
variar un poco la manera habitual con la que la usaremos en este ejemplo, apoyándonos en los ES6
Modules.
[Link]
Con el código de una página básica, donde voy a volcar el contenido de los templates generados. Realmente
es un archivo HTML prácticamente vacío, pero creo que es bueno ver el código porque hace uso de un
script que se carga como módulo de ES6, que quizás sea desconocido por algunas personas.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Lit-html Demo</title>
</head>
<body>
<div id="ej1"></div>
<div id="ej2"></div>
<div id="ej3"></div>
</body>
</html>
El script de Javascript donde realizamos el procesamiento para el uso de Lit-HTML lo estamos trayendo
como un módulo, para poder realizar imports de ES6. Observarás el atributo type="module".
Nota: por si no los conoces, te recomendamos un artículo de DesarrolloWeb donde puedes aprender
más sobre los módulos ES6.
Luego verás en el [Link] tres contenedores donde vamos a volcar el contenido de las templates una vez
asignados valores a sus expresiones. Fíjate que tienen identificadores definidos, pues vamos a referirnos a
ellos desde el Javascript.
[Link]
La gracia de este ejercicio la tenemos en el Javascript que hace uso de Lit-HTML. Aquí es donde vamos a
generar un par de templates y vamos a producir un HTML a partir de la carga de datos para alimentar sus
expresiones.
El primer paso consiste en obtener las cosas que necesitamos para trabajar con Lit-HTML, con un import
de ES6.
######### html:
Es el tag para los "tagged template string". Es, como dijimos antes una función que se aplicará a la template
string que enseguida vamos a crear.
######### render:
Es una función Javascript que sirve para volcar el HTML generado con los templates dentro de la página.
Render recibirá dos parámetros.
Construir un templateResult
Podemos construir un template usando el tagged template string, al que le tenemos que aplicar los datos
para poblar sus expresiones.
En las dos líneas anteriores, primero creamos una variable con el dato que queremos asignar dentro del
template. En la segunda línea creamos el objeto "templateResult" procesando el tagged template string, en el
que verás que hemos colocado una expresión donde tiene que aparecer el dato "sitioWeb".
Sin embargo, este código será más versátil si creamos una función que reciba el dato que se desea volcar el
el template string. Invocando luego a esa función con un resultado cada vez que se pretenda generar el
templateResult.
Renderizar un template
Ahora nos falta renderizar el template en algún lugar de la página. Para ello usamos la función render() de
Lit-HTML, con el templateResult anterior y algún nodo donde deba volcarse el HTML.
render(objetoTemplateResult, [Link]);
Entendiendo todos los pasos anteriores debrería de quedar más o menos claro el código siguiente, que pone
todo el conjunto de acciones en funcionamiento para usar Lit-HTML.
render(miTemplate('[Link]'), [Link]('ej1'));
render(miTemplate('EscuelaIT'), [Link]('ej2'));
Hemos volcado los dos bloques de HTML generado en dos elementos de la página, los que tenían los
identificadores "ej1" y "ej2". Por eso el resultado que veríamos es algo como este:
Lo que acabamos de ver es simplemente el inicio de las funcionalidades que hay detrás de esta librería.
Ahora vamos a ver cómo de versátil puede ser el sistema de expresiones de los template strings para
mostrar código que viene desde un array.
Este ejemplo, aunque todavía muy básico, es muy interesante, porque nos demuestra que los templates de
Lit-HTML se pueden anidar, colocando nuevos templates dentro de expresiones.
De hecho, una de las claves de la potencia de Lit-HTML es que usa expresiones Javascript nativas. Es decir,
en los bloques de las template strings, ${ expresion }, se puede colocar cualquier código Javascript que
realice cualquier cosa que se nos ocurra.
return html`
<ul>
${[Link]((interes)=> html`<li>${interes}</li>`)}
</ul>
`;
En este segundo template lo que tenemos es que la variable "intereses" será un array. Por tanto lo podremos
recorrer dentro de la expresión del template string, produciendo para cada ítem un nuevo template con
HTML que se repetirá tantas veces como casillas en el array.
Podremos dar uso a este template y volcarlo en cualquier nodo del documento.
Conclusión
Lo que hemos aprendido es sólo el inicio de la funcionalidad que hay detrás de Lit-HTML. Es todavía una
librería experimental que se está acabando de perfilar en estos momentos para formar parte de Polymer,
aunque podría ser usada en cualquier ámbito del desarrollo Frontend.
Hemos dejado de lado mucha información interesante, que da nuevas claves de lo versátil y avanzado de
este sistema de templates en Javascript y las muchas cosas que se puede hacer con él para llegar mucho más
lejos.
Polymer 3 usará Lit-HTML, pero le pone por encima nuevas extensiones (algo factible de conseguir ya que
el propio Lit-HTML se ha construido de manera que pueda ser extensible) y conseguirá que trabajemos con
este sistema de templating sin que tengamos que preocuparnos por mucha de su complejidad, adaptándolo
más aún a la experiencia amigable de desarrollo que nos tiene acostumbrados Polymer.
En futuros artículos explicaremos otras parcelas interesantes de Lit-HTML y podremos tener nuevos flashes
de las cosas que están por llegar y que pondrán sin duda a Polymer aún más en la mira de todos aquellos
desarrolladores preocupados por el rendimiento de sus aplicaciones y el uso de las posibilidades nativas de
la plataforma web.
Uniflow Polymer
Qué es Uniflow, un patrón de arquitectura para aplicaciones Polymer 2 inspirado en el patrón Flux
y Redux.
En este artículo vamos a ofrecer una introducción a Uniflow, un nuevo complemento para las aplicaciones
Polymer, con el que podremos realizar una arquitectura de aplicación más depurada, escalable y mantenible.
Uniflow de Polymer toma las ideas y conceptos lanzados por Flux para aplicaciones ReactJS. También sería
similar a Redux, pero directamente pensada para usar en Polymer 2. Sin embargo, hay que aclarar que
Uniflow y Polymer no están desarrollados por el mismo equipo. A pesar que Uniflow Polymer es también
un proyecto de Google, sus desarrolladores no pertenecen directamente al equipo de Polymer. Aunque,
dicho sea de paso, el equipo de Polymer ha prestado bastante atención a Uniflow y ha depositado muy
buenos comentarios en la comunidad.
Desarrollar una aplicación Polymer es una experiencia sencilla para el programador. Sus mecanismos de
binding y el tratamiento de eventos son excelentes mecanismos para comunicación entre componentes,
pero a veces, si no se mantiene una organización, el código puede hacerse difícil de mantener.
Sobre todo en proyectos grandes, donde la complejidad aumenta y se tiene muchos componentes que se
relacionan entre sí, se puede hacer difícil la depuración del código y la ampliación de funcionalidad.
Uniflow propone una arquitectura que, una vez implementada permite una asimilación más sencilla del
esquema de relaciones entre componentes y con ello facilita el mantenimiento y la comprensión del flujo de
los datos dentro de la aplicación.
Qué es Uniflow
Básicamente son una serie de mixins, creados para mantener el estado de la aplicación de una manera
controlada. Aplicados a los elementos de la aplicación estos mixins nos permiten disponer de un API
específicamente pensado para facilitar la colaboración entre componentes, aplicando el patrón "Flux".
El patrón Flux, popularizado por React, tiene como característica fundamental el flujo de datos
unidireccional dentro de la aplicación.
Dentro de uniflow existe un componente que recibe todas las acciones y es capaz de manipular el dato. Una
vez manipulado, el valor nuevo del dato viaja por data-binding a los componentes hijos, para ser
representado en todos los lugares de la aplicación donde sea usado.
Para aprender más sobre la implementación de Uniflow en el marco de una aplicación Polymer, vamos a
realizar un sencillo proyecto, donde podamos aplicar algunos de los mixins que nos facilita esta librería.
Vamos a mantener sencilla esta primera aproximación, para facilitar las cosas. Por ese motivo en nuestra
aplicación ni siquiera vamos a usar todos los mixins que tenemos disponibles en Polymer Uniflow. Sin
embargo, confiamos que esta primera aproximación sirva para entender cuál es el uso de Uniflow y cómo
implementa el flujo de información en nuestra aplicación.
El ejemplo que vamos a crear es un sencillo contador de clics. Solo tendremos un botón y un texto que nos
dice el número de clics realizados sobre el botón. Al hacer clic sobre el botón se incrementará en uno el
contador, y se representará inmediatamente en la aplicación.
Crearemos un nuevo proyecto de Polymer. Comando "polymer init" en el Polymer CLI. Podemos escoger
el template de aplicación básico, para no despistarnos con cosas que no tengan que ver con el propio
Uniflow.
Estado de la aplicación
Existe un único lugar donde se almacena el estado de la aplicación. Este lugar corresponde generalmente al
componente raíz de la aplicación.
El estado es como cualquier otra propiedad. Será un objeto que contendrá una cantidad de subpropiedades
con los datos que queremos manejar como estado de la aplicación. No necesitamos declarar la propiedad,
puesto que los mixins que iremos incluyendo ya lo hacen por nosotros. Sin embargo, la podemos inicializar
en el método ready() de nuestro componente raíz.
ready() {
[Link]();
[Link]('state', {
clics: 0
});
De manera general, a lo largo de todo el proyecto, si queremos traspasar el estado de un componente a otro,
lo hacemos por medio de binding de 1 sola dirección, en Polymer expresado por los corchetes [[]].
<muestra-clics clics="[[[Link]]]"></muestra-clics>
Mixin ApplicationState
El componente raíz de la aplicación, o aquel que deba manejar el estado de manera global, debe usar el
mixin "ApplicationState".
Emisores de acciones
Existirán componentes que deban modificar el estado de la aplicación. Sin embargo no lo harán por ellos
mismos, ya que para modificar el estado siempre se debe realizar la invoación de una acción.
Todos los componentes que deban modificar el estado serán emisores de acciones. Para ello emitirán
acciones que en el fondo son eventos, que suben hasta el componente raíz usando la burbuja de eventos del
navegador.
Los componentes que emiten acciones deben usar el mixin "ActionEmitter", que está entre el conjunto de
clases que nos ofrece Polymer Uniflow.
Luego tendremos que declarar el uso del mixin en el componente que va a emitir acciones:
Por último, se lanzan las acciones. Al lanzar la acción debemos de indicar algunos datos. Al menos será
necesario enviar el tipo de la acción que se desea ejecutar.
[Link]({
type: 'AGREGAR_CLIC'
});
Ahora vamos a hablar del componente Action Dispatcher, que se encarga de procesar las acciones. Este es
un componente no-representacional (que no tiene ningún elemento visual en su template para presentación)
y generalmente será un hijo directo del componente raíz de la aplicación. Podría apoyarse en otros
componentes para realización de las acciones, aunque de momento no es el caso porque tenemos una sola
acción a implementar.
Este componente recibe el estado de la aplicación, enviado por doble binding desde el componente raíz.
<contador-clics-action-dispatcher state="{{state}}"></contador-clics-action-dispatcher>
Nota: Este es el único sitio donde usamos doble binding del estado de la aplicación, ya que el action
dispatcher es el único sitio donde queremos modificarlo. Para el resto de los componentes donde
pasemos el estado deberíamos usar binding de una sola dirección.
Comenzamos importando:
En el action dispatcher encontraremos un método para implementar cada acción. El método tiene el mismo
AGREGAR_CLIC(details) {
En los métodos de las acciones está permitido modificar el estado. Realmente, es el único sitio donde se
debería hacer. Como el estado definido en el action dispatcher tiene un doble binding con el estado general,
definido en la raíz de la aplicación, cualquier modificación se trasladará a todos los componentes de la
aplicación.
El nombre de la acción lo tenemos escrito a fuego en el código y puede ocurrir que se repita varias veces a
lo largo de la aplicación. Por ello sería una buena idea declararlos de manera global para toda la aplicación.
Lo podemos hacer de diversas maneras, por ejemplo, yendo a lo sencillo, en un archivo de Javascript plano,
que incluiremos en el [Link].
Este sería el código para la definición de los nombres de las acciones, a través de un objeto que contenga
todas las declaraciones de nombres de funciones.
[Link] = {
AGREGAR_CLIC: 'AGREGAR_CLIC'
Este código lo podremos situar en un fichero independiente llamado "[Link]" y por supuesto tiene que
estar enlazado en algún sitio. Habíamos dicho que en el [Link] podría servir:
<script src="scripts/[Link]"></script>
Una vez definidos los nombres de las acciones podremos usarlos, tanto al invocar las acciones como al
implementarlas.
Invocamos las acciones así (recuerda que esto lo haces en el componente que habíamos definido con el
mixin [Link]):
[Link]({
type: [Link].AGREGAR_CLIC
});
Ahora podemos implementar las acciones así (Recuerda que esta implementación se encuentra en el
[[Link].AGREGAR_CLIC](details) {
Conclusión
Lo visto en este artículo son las líneas generales de uso de Polymer 2 Uniflow. Si tienes una idea al menos
ligera sobre el desarrollo de aplicaciones con Polymer seguramente puedas construir tu propio ejemplo a
partir de estas explicaciones.
Además, sabemos que apreciarás ver el código completo de esta aplicación, para resolver posibles dudas. Lo
tienes en Github: [Link]