Angular para páginas WEB y la recarga de contenidos
Tal y como os había comentado en una entrada anterior, las aplicaciones SPA (Single Page Application – Aplicaciones de página única) están de moda. Bueno, más que de moda, son una muy buena opción a la hora de desarrollar una aplicación para la WEB, pero, ¿y si le damos una vuelta de tuerca y consideramos toda página web una aplicación?
Esto es lo que pretendía comentar en esa entrada, la viabilidad de esta opción a la hora de desarrollar una página WEB.
Una SPA supone llevar la mayor parte de la lógica al cliente. El o la usuaria de esa aplicación navegará por las distintas opciones de la aplicación, lo que implica la ejecución de acciones y el correspondiente envío, recepción, procesamiento y presentación de datos. Como puedes apreciar, una página WEB hace exactamente esto, recibe y presenta información de un servicio ante las acciones (direcciones) del o de la visitante.
Visto así no parece que haya ningún problema para desarrollar un página WEB mediante una SPA, pero existe una diferencia importante, y es que una aplicación siempre tiene un mismo punto de entrada que suele corresponder con la pantalla de identificación (login). En cambio, una página WEB puede ser accedida desde cualquier punto, debiendo mostrar la información que la o el usuario demanda. En este sentido se nos hace necesario que nuestra aplicación reaccione adecuadamente ante las diferentes llamadas e independientemente de cuál haya sido el recorrido que se ha seguido hasta alcanzar esa página.
Igual os estoy liando un poco, así que voy a intentar poner un ejemplo.
Supongamos que nuestra WEB se basa en noticias. Está claro que dispondrá de una página principal que muestra las noticias destacadas y un sistema de paginación que nos permita acceder a noticias de otros días.
Con una aplicación SPA es sencillo hacer este comportamiento, simplemente diseñamos un control (módulo) que liste las noticias y que reaccione a los botones de avance de página y de retroceso. Esto permite que el o la visitante acceda a todos los contenidos, pero OJO, aquí está el detalle, la mayoría de frameworks que permiten el desarrollo de estas SPA utilizan variables internas para permitir la navegación, es más, muchas de ellas ni permiten la navegación hacia atrás con los botones del propio navegador. Cierto, solo pasa con frameworks antiguos.
Ahora supongamos que un robot (un bot, un crawler o una araña) visita nuestra web y navega (si es que puede) entre las opciones de paginación. Lo más seguro es que estas páginas internas no entren en el índice del buscador de forma adecuada puesto que mantienen la misma URL. ¿Y qué pasa si un contenido no se indexa? pues que no se devuelve como resultado en las búsquedas y en la práctica, que no exista para el público. Además, el servicio de búsqueda (por ejemplo Google) puede considerar que nuestro portal web tiene poco contenido y por tanto no le dará la relevancia adecuada lo que hará que pierda posicionamiento y nuevamente no sea alcanzable por la mayoría de nuestro público objetivo o potencial.
Fijaros que no me meto con el marcado que genera nuestro sistema, que normalmente será una página vacío que se irá componiendo sobre la marcha en base a las peticiones que hagamos.
-¿Me estás contando que no se puede hacer una página WEB con SPA?- De momento no estoy diciendo que no se pueda, sino más bien que hay que prestar atención a este y otros detalles que iremos descubriendo más adelante, como son el tema de la recarga o el SEO.
Pero el tema que nos ocupa ahora es corregir el comportamiento de la carga de contenidos concreta y el indexado en los buscadores. ¿Como? pues fácil, hay que tener siempre presente que cada acción se ha de ver reflejado en un cambio de dirección (url). Y aunque parece sencillo hay veces que no lo es tanto, pero no por ello debemos olvidar prestar cuidado en que todo funcione lo mejor posible.
Cada acción con su dirección
En la WEB tradicional era un hecho que toda petición tenía una dirección propia. Es cierto que al principio buscadores como Google gestionaban cada dirección junto con sus parámetros de forma independiente, lo que generaba ciertos conflictos como la duplicidad de contenidos por poner un ejemplo. En su momento Google cambió su algoritmo para penalizar las URL basadas en parámetros, lo que hizo cambiar la estrategia de los gestores de contenidos (CMS) a direcciones más descriptivas. Pasamos de la dirección “mi_domino/evento?id=25” a “mi_dominio/evento/evento_uno”. Este cambio condujo al concepto de “url amigable”, ya que este tipo de direcciones permite que la gente pueda recordarlas de forma sencilla, a la vez que se generan direcciones permanentes y unívocas.
Sin embargo la tendencia de l@s usuari@s cambia como la moda y en la actualidad nadie recuerda ninguna dirección, simplemente utilizan el buscador incorporado en la barra de navegación para acceder a un contenido. También están los acortadores de dirección, que permiten traducir una dirección larga por otra menos legible pero útil en las redes sociales. Por cierto, Google cerró su sistema acortador de direcciones hace unos años, y si Google lo ha hecho es por alguna razón bien meditada.
Independientemente de si el usuario recuerda una dirección o no, tenemos que pensar que Google utiliza toda la información de nuestra web para darle un peso o ranking a nuestro contenido, y que las URL son parte de esa información. Por tanto una buena dirección también ayuda (o perjudica) al posicionamiento de nuestro contenido.
Direcciones sencilla y mejor posicionamiento, que bien suena, lastima que no sea todo tan fácil como parece.
Si lo pensamos en términos de una SPA “tradicional” (entended que la tecnología no es tan antigua como para llamarla tradicional y por eso la pongo entre comillas), cada acción está bien definida y por tanto podrá disponer de su propia URL que vendrá soportada por su correspondiente regla de ruta.
El problema lo tenemos cuando estamos desarrollando una SPA para una página WEB. En este caso la información a presentar vendrá de un CMS y muy probablemente de un WYSIWYG. Que usemos un editor HTML no es un problema, de hecho sería un problema si no permitimos a los gestores y gestoras de contenidos una herramienta para dar formato al contenido. El problema es que a priori no sabemos en donde se encuentran las direcciones y por tanto no podemos aplicar ninguna regla que permita redefinir el comportamiento de nuestra SPA. ¿O sí?
Angular y la recarga de contenidos
Al principio de esta serie de POST, indiqué que nuestro SPA estaría basado en Angular, por lo que todas las ideas que ponga sobre la mesa serán basadas en las limitaciones o ventajas de este Framework.
Además, debemos tener claro que nuestra estrategia consistirá en evitar una carga excesiva en el cliente (navegador o móvil) tanto a nivel de procesamiento de página como a nivel de transferencia de datos.
Cuando desarrollamos una aplicación en Angular se requiere de una compilación que genera unos pocos ficheros con toda la lógica de nuestro aplicativo y enviados al cliente en cada petición, aunque por suerte existen una serie de procesos que evitan la carga de información repetida, las cachés.
Como he dicho, una caché evita al cliente (navegador o móvil) la descarga repetida de los mismo datos. Por ejemplo hay cachés en los servidores para procesar las solicitudes repetidas de los usuarios de forma más eficiente y rápida. Hay caches en los routers intermedios y en el de nuestro proveedor de internet para acelerar la comunicación ante la misma petición de l@s client@s, y por último hay una caché en nuestro navegador. Todas estas cachés son, en principio, transparentes para nosotros, aunque existen técnicas para evitarlas y de hecho muchos frameworks hacen uso de estas técnicas para la obtención de los datos. En resumen una caché no es más que una instantánea o copia en un instante de tiempo que es enviado o utilizado. Cada vez que obtenemos datos puede ser necesario realizar operaciones costosas, como acceder a una base de datos, cruzar información entre diferentes fuentes o realizar operaciones con los datos antes de ser enviados. El objetivo es hacer la operación una primera vez, quizás cuando se haga la primera operación o quizás una vez al día cuando la máquina esté ociosa, todo dependerá de las necesidades que tengamos. La cuestión es que al tener una instantánea podemos simplemente enviar los datos ya procesados y de esta forma ahorrar tiempo. El problema es cuando los datos cambian con mucha frecuencia y las distintas caches nos provocan desajustes ante estos datos tan cambiantes.
Es interesante conocer cómo funcionan las caches y es más interesante saber que en la mayoría de los casos vamos a beneficiarnos de una carga rápida de nuestra WEB. Sin embargo Angular no parece que tenga, al menos en la versión actual, un buen mecanismo de código reutilizable. Esto lo digo en el sentido de que una parte de las aplicaciones de Angular utilizan siempre los mismos módulos y aún así, al compilar nuestra aplicación se generan ficheros locales tanto para esos módulos como para nuestro aplicativo. Para los despistados, la mejor opción siempre será la de utilizar direcciones de uso masivo (CDN), lo que favorece la posibilidad de que el módulo a utilizar ya se encuentre descargado por el cliente y por tanto la carga sea más rápida y con menos transferencia de datos.
Pero quitando este “pequeño” detalle, tenemos que entender que aunque existe una caché la carga de la página no es instantánea ya que existen peticiones entre el cliente y el servidor al menos para verificar que los ficheros no han cambiado.
Recapitulando
Angular es un framework que nos permite desarrollar aplicaciones de página única (SPA) y que envía toda la lógica al cliente una primera vez con la intención de hacer llamadas únicamente para recuperar datos y adaptarse a ellos. Por otro lado una página WEB dinámica o gestionable, requiere de la capacidad de edición de texto, lo que implica que el contenido vendrá etiquetado con las necesidades del personal gestor en los que se incluyen enlaces a contenidos. Por último, para ofrecer una buena experiencia al o a la visitante de nuestra web, el comportamiento de la aplicación deberá requerir el menor número de solicitudes y con el menor coste en tráfico.
El problema y una solución
Visto todo lo anterior, solo queda definir el problema. Este se encuentra en el comportamiento de Angular a la hora de tratar los enlaces.
Para los enlaces de acción internos Angular dispone de la propiedad (routerLink). Esta propiedad que pertenece a los enlaces es convertida en tiempo de compilación por una combinación de eventos javascript que comprueba y evitan la recarga de la página si ese enlace se encuentra definido o cumple una de las reglas de enrutado. Y sí, esto se hace en tiempo de compilación por lo que tenemos que tener muy claro dónde están estos enlaces, coso imposible si estos vienen de un CMS con editor de contenidos HTML.
Aquí os propongo dos soluciones, una que personalmente he aplicado y funciona muy bien, y otra que implica un cambio severo en el funcionamiento de los CMS y por tanto no está probado, pero sería una buena línea de desarrollo (me lo apunto ;).
Controlando enlaces
Esta primera solución es la más obvia aunque no por ello compleja. Consiste en analizar el contenido de cada campo de texto devuelto por el CMS en busca de enlaces. Esta operación es muy sencilla ya que simplemente deberemos buscar las cadenas, el problema es que buscar de esta forma además de lento puede generarnos otros problemas.
Lo que yo hago es crear una capa, ya sabéis con “document.createElement(“div”)” y cargarle el contenido devuelto por el CMS. Esto produce una estructura de objetos los cuales permiten la búsqueda eficiente. El siguiente paso es procesar los objetos, concretamente hoy hablaré de los enlaces.
Una vez cogidos los enlaces, simplemente comprobamos el valor del “href” y decidimos si el contenido se carga localmente o se requiere una carga completa.
Si es una carga completa nuestro trabajo ya habrá acabado, pero si es una carga “local” hemos de continuar con el proceso.
Alguno estará pensando, ¿Porqué no pones ahora un “a” con un “routerLink”?, la respuesta es fácil, el “routerLink” solo funciona en tiempo de compilación y ahora nos encontramos en tiempo de ejecución por lo que el navegador y nuestra SPA no entienden ese parámetro o propiedad.
Mi solución consiste en generar un evento “Click”, el cual proceso a posteriori mediante un código en el propio angular. El problema es dónde colocar ese evento, y la solución es clara, al menos una vez que sabes dónde vá. Sí, lo colocamos en el contenedor en el que incluimos el HTML devuelto por el CMS.
Ahora simplemente analizamos quién generó el “Click” dentro de ese contenedor y procedemos a decidir si hacer un enlace normal o un cambio de ruta.
interaccion(ev){ let enlace=null if(ev.target){ if(ev.target.nodeName=="A"){ if(ev.target.getAttribute("data-link")=="interno"){ enlace=ev.target; } }else{ //Miramos si algún padre lo es let niveles=5; let p=ev.target.parentNode; while(p.nodeName!="A" && niveles-->0){ p=p.parentNode; } if(p.nodeName=="A" && p.getAttribute("data-link")=="interno"){ enlace=p; } } } if(enlace && !ev.ctrlKey && !ev.shiftKey){ //Es un enlace ev.stopPropagation(); this.router.navigateByUrl(enlace.getAttribute("data-href")); return false; } }
Estás interesado o interesada en que haga un ejemplo en vídeo, si es así ponme un comentario. También puede comentar cualquier cosa de la que te gustaría que escribiese o en la que te gustaría que preparase un vídeo o un directo.
Espero vuestros comentarios.