Componentes del sistema de routing en Polymer

  • Por
Analizamos con ejemplos sencillos los componentes app-location y app-route, para comenzar a comprender cómo se interpretan las rutas de aplicaciones SPA en Polymer.

Con Polymer nos encontramos ante una librería de Web Components, pero lo cierto es que su ámbito se acerca a lo que nos ofrece un framework, sobre todo por su amplia colección de elementos disponibles. Uno de los ejemplos es el sistema de routing.

Lo más espectacular, a mi juicio, es que en Polymer han conseguido modelar el sistema de routing basándonos en etiquetas (componentes) que podemos configurar de manera declarativa. Mientras que otros sistemas requieren el uso de Javascript para poder definir el sistema de routing, aquí vamos a necesitar editar simplemente el HTML. Quizás se pueda decir que ésto no es más que la filosofía intrínseca de los Web Components, pero lo cierto es que a mi no me deja de sorprender.

Vamos a ver en este artículo cómo está compuesto el sistema de routing y cómo configurarlo. Pero antes y por si alguien no lo sabe todavía, el sistema de routing permite en páginas del tipo "Single Page Application" (SPA) disponer de rutas internas a secciones concretas de la aplicación. En las SPA todas las ventanas de la aplicación en realidad son la misma página con vistas que se van intercambiando, por decirlo de otra manera, todo se produce dentro del index.html. Sin embargo, por diversos motivos es interesante que nuestros usuarios puedan entrar directamente en una página interna, como la ficha de un producto, el perfil de un usuario o la página de contacto. Esto se consigue con rutas, URL internas que se escriben en la barra de direcciones del navegador, las cuales la aplicación es capaz de interpretar para presentar al usuario la pantalla correcta dentro de la aplicación.

Componentes que forman el sistema de routing

Comencemos analizando los componentes que forman parte del sistema de routing y con los que podemos conseguir las deseadas rutas en una SPA. Son dos:

app-location

Este componente permite analizar la barra de direcciones del navegador, generando un objeto que modeliza el estado actual de la barra, es decir, la URL que hay escrita. Es capaz de exponer hacia el sistema de binding de Polymer el estado de la barra de direcciones y así mismo, en el momento en el que el objeto cambia dentro de la aplicación, repercutir ese estado hacia la barra de direcciones.

Por decirlo de otra manera, app-location hace de puente entre la barra de direcciones y la aplicación, por medio de un atributo llamado "route". Si se actualiza la barra de direcciones app-location cambia el valor del objeto "route" y si dicho objeto cambia, app-location actualiza el estado de la barra.

app-route

El componente app-route toma el valor de la ruta proveído por el app-location y lo descompone, de modo que nos resulte sencillo analizarlo, exponiendo el estado de la ruta al sistema de binding de manera totalmente personalizada por el desarrollador.

Básicamente en app-route vamos a definir un patrón (pattern) que se puede buscar en las rutas y si ese patrón está presente descompone la dirección en varias partes que nos permitan su análisis y utilización dentro de la aplicación. Luego entraremos en detalle, pero esa descomposición producirá dos objetos, uno de datos encontrados en el patrón (atributo data) y otro con datos que no se llegaron a analizar en el mismo (atributo tail).

Mientras que sería lógico tener solo un app-location por aplicación, es normal que tengamos varios app-route para descomponer los datos de la URL atendiendo a varios patrones posibles de rutas.

Antes de comenzar a usar estos componentes, como ya debes saber, debemos instalarlos dentro de nuestro proyecto, con el correspondiente comando de bower. Se instalan ambos en un solo comando, ya que forman parte del mismo bundle.

bower install --save PolymerElements/app-route

A continuación vamos a hacer algunos ejemplos simples que nos permitan entender estos componentes de manera práctica y sencilla.

Obviamente, también tendrás que hacer los correspondientes import, para que tu aplicación sea capaz de reconocerlos.

<link rel="import" href="../bower_components/app-route/app-location.html">
<link rel="import" href="../bower_components/app-route/app-route.html">
Nota: Antes de comenzar con los ejemplos es importante también señalar que las rutas de la aplicación dependen del servidor web que se esté usando para servir las páginas. No todos los servidores están configurados para usar rutas internas de la aplicación como una SPA. Básicamente el servidor tiene que hacer internamente el trabajo de, sea cual sea la ruta de la barra de direcciones, redirigir todo el tráfico hacia el punto de entrada de la aplicación (el index.html). Si usas el servidor integrado en el CLI de Polymer (comando "polymer serve") esto lo tienes ya disponible, pero si tu servidor no es capaz de funcionar en el estilo de las SPA quizás estés obligado a usar el "#" para las rutas. Esto no sería problema ya que app-location tiene una propiedad llamada "useHashAsPath" que permite que ese reconocimiento de las rutas con "#" sea transparente.

Ejemplo con app-location

En nuestro primer ejemplo vamos a trabajar únicamente con el componente app-location, para reconocer la ruta y saber cómo es el objeto que te ofrece para modelizarla.

Para usar este componente solamente necesitamos un código como este:

<app-location route="{{route}}"></app-location>

Como ves, el atributo "route" te ofrece hacia el sistema de binding un valor, que es un objeto donde podremos encontrar información sobre la ruta.

Para poder experimentar con app-location en este ejemplo vamos a comenzar colocando dos botones sencillos que hagan cosas.

<button on-tap="mostrarRoute">route a consola</button>
<button on-tap="cambiarRoute">cambiar route</button>

Primero tenemos un botón que, pulsado, enviará a la consola el objeto de la ruta.

Nota: Realmente este objeto no nos tiene que preocupar, puesto que nosotros no vamos a tener que trabajar con él manualmente, sino que lo entregaremos tal cual al componente app-route. Solo queremos ver qué es lo que nos ofrece para entender mejor su trabajo.

El manejador de evento del primer botón es el siguiente método del componente:

mostrarRoute: function() {
  console.log(this.route);
},

Si estamos en la ruta raíz de la aplicación, el objeto de la ruta tendría esta forma.

En cuanto, si estamos en la ruta "/productos" tendría esta otra forma.

Como puedes ver, el "path" ha cambiado. También tiene un atributo "prefix" que está siempre a vacío. Más adelante veremos para qué sirve este "prefix".

Ahora vamos a ver el otro manejador de eventos, para el segundo botón. Básicamente lo tenemos para modificar programáticamente el valor del objeto "route". Como sabes, al modificarlo, repercutirá en el estado de la ruta en la barra de direcciones.

cambiarRoute: function() {
  this.set('route.path', '/otra/66');
},
Nota: Usamos this.set() ya que estamos modificando una propiedad interna de un objeto y queremos que esa modificación avise a todos los componentes relacionados en el sistema de binding. Usar this.set() es necesario para que esa modificación tenga respuesta en todos los lugares donde se usa el objeto route.

Al cambiar la propiedad "path" del objeto "route" el componente app-route se debe encargar de modificar la barra de direcciones, viajando a la ruta "/otra/66". Si luego volvemos a inspeccionar el valor del objeto route en la consola nos dirá que su path a cambiado y lo podremos ver también reflejado en URL a la que el navegador está situado.

El código completo de este ejemplo lo puedes ver a continuación.

<link rel="import" href="../bower_components/polymer/polymer.html">
<link rel="import" href="../bower_components/app-route/app-location.html">

<dom-module id="test-app-location">
  <template>
    <style>
      :host {
        display: block;
      }
    </style>
    <h1>Test APP-LOCATION</h1>
    <app-location route="{{route}}"></app-location>
    <button on-tap="mostrarRoute">route a consola</button>
    <button on-tap="cambiarRoute">cambiar route</button>

  </template>
  <script>
    Polymer({
      is: 'test-app-location',

      properties: {
        route: Object
      },

      mostrarRoute: function() {
        console.log(this.route);
      },
      cambiarRoute: function() {
        this.set('route.path', '/otra/66');
      },
    });
  </script>
</dom-module>

Ejemplo con app-route

Para acabar con esta primera zambullida en el sistema de routing de Polymer vamos a conocer el componente app-route, que se encarga de interpretar el objeto "route" generado por el "app-location" y exponer el valor de la ruta en caso que se ajuste a un patrón.

Para que ambos componentes funcionen juntos es necesario bindear el objeto "route" de un componente a otro, con binding de dos direcciones, de modo que los cambios se entreguen de una parte a la otra.

<app-location route="{{route}}"></app-location>
<app-route
  route="{{route}}"
  pattern="/:page"
  data="{{data}}"
  tail="{{tail}}">
</app-route>

El componente app-route tiene otra serie de propiedades que debemos explicar:

  • route: Es el objeto de la ruta, que nos ha generado el app-location.
  • pattern: Es el patrón buscado en la ruta. Si lo que hay escrito en la barra de direcciones concuerda con este pattern, entonces app-route descompondrá esa URL en varios pedazos.
  • data: Todos los datos que se ajusten a la definición del patrón, se guardarán en el objeto data. Nuestro patrón se definió como "/:page", lo que quiere decir que el segmento que hay detrás de la raíz de la aplicación se meterá en el objeto "data" con el atributo "page".
  • tail: Es todo lo que no se ha reconocido en el patrón anterior. Todo aquello que sobró, se coloca dentro del objeto tail, el cual nos vendrá muy bien para hacer rutas profundas de aplicaciones, pero que no vamos a analizar todavía en este artículo.
Nota: En este caso el patrón era "/:page", que solo tiene un segmento, pero podría ser más complejo. Casando este patrón el objeto data tendrá una única propiedad llamada "page", pero si el patrón fuese algo como "/:page/:id" entonces al detectarse el patrón el objeto "data" se producirá con dos propiedades, "page" e "id".

Nuestro componente de ejemplo tiene ahora un botón para mostrar qué hay en el objeto "data" producido.

<button on-tap="mostrarData">Data a consola</button>

El manejador de eventos hace un simple "console.log" para enviar el objeto "data" a la consola.

mostrarData: function() {
  console.log('Data:', this.data);
},

Ahora podemos experimentar con diversas rutas y ver lo que pasa.

Ruta raíz "/"

La llamamos la ruta "/" pero lo más seguro es que en tu barra de direcciones tengas la palabra localhost o cualquier otro dominio como "http://localhost:8080/". En este caso la ruta concuerda con el patrón, aunque "page" tiene un valor vacío.

Ruta /agenda/

Esta ruta concuerda con el patrón. Entonces data se llenará con el valor del patrón reconocido.

Nota: En este momento es indiferente, con una ruta que solo tiene un segmento, pero más adelante es posible que necesites la barra al final para concordar el patrón. Es decir algo como "http://localhost:8080/agenda" puede no ser lo mismo que "http://localhost:8080/agenda/" (con la barra al final). Generalmente colocar los href de tus enlaces con la barra al final te puede salvar de algunos problemas.

Ruta /productos/

Como en el caso de antes, sí concuerda con el patrón y podemos recuperar en data.page el valor de la página "productos".

Ruta /productos/45/

En esta nueva ruta tenemos también el patrón "/:page", aunque hay más datos a reconocer que van más allá del propio patrón. El objeto "data" seguirá teniendo el mismo valor, que en el caso anterior.

Pero ahora el tail, del que no hemos hablado mucho todavía, contendrá alguna información extra, con toda la parte de la URL no reconocida con el patrón.

Nota: Del tail hablaremos más adelante, así que de momento que no te preocupe demasiado. Solo observa que te expone de nuevo toda la ruta de la barra de direcciones, pero descompuesta de otra forma.

Ahora te dejamos el código completo del segundo componente de ejemplo, para ver el app-route en funcionamiento.

<link rel="import" href="../bower_components/polymer/polymer.html">
<link rel="import" href="../bower_components/app-route/app-location.html">
<link rel="import" href="../bower_components/app-route/app-route.html">

<dom-module id="test-app-route">
  <template>
    <style>
      :host {
        display: block;
      }
    </style>
    <h1>app eit</h1>
    <app-location route="{{route}}"></app-location>
    <app-route
      route="{{route}}"
      pattern="/:page"
      data="{{data}}"
      tail="{{tail}}">
    </app-route>
    <button on-tap="mostrarData">Data y tail a consola</button>

    
  </template>
  <script>
    Polymer({
      is: 'test-app-route',

      properties: {
        route: Object,
      },

      mostrarData: function() {
        console.log('Data:', this.data);
        console.log('Tail:', this.tail);
      },
      mostrarTail: function() {
      },
    });
  </script>
</dom-module>

Conclusión

Hemos comenzado a entender el sistema de routing de Polymer con ejemplos muy simples. De momento solo nos hemos preocupado por conocer los dos componentes que usaremos para trabajar las rutas de la aplicación, aunque quizás sea un misterio todavía cómo usarlos para poder intercambiar las vistas de tu app.

Todavía tendremos que estudiar un poco más también para poder reconocer rutas complejas, pero ya será en la próxima entrega.