Aunque AngularJS viene con enrutamiento incorporado, a veces puede encontrarlo limitante; el framework Angular UI Router puede ayudar a aliviar el dolor. La implementación nativa de enrutamiento de AngularJS es responsable de inicializar los controladores que coinciden con las rutas de la aplicación. Aunque esta funcionalidad funciona bien en escenarios básicos, en situaciones avanzadas, rápidamente se encuentra que el enrutamiento nativo de Angular:
- Le obliga a cambiar manualmente las cadenas de URLs en toda su base de código cuando una URL cambia.
- Le obliga a recordar la sintaxis de la ruta textualmente para navegar a una página.
- No ofrece vistas anidadas.
- No ofrece vistas con nombre.
- No permite pasar datos durante la navegación.
Como alternativa, el framework Angular UI Router es una capa de abstracción para el enrutamiento que presenta un enfoque más declarativo de la navegación. El framework UI Router también rellena algunas de las lagunas de la implementación nativa proporcionando vistas anidadas y con nombre, permite pasar datos entre vistas, y mucho más.
El framework Angular UI Router es una capa de abstracción para el enrutamiento que cuenta con un enfoque declarativo para la navegación
En este artículo, aprenderás a construir una aplicación simple que utiliza el framework UI Router. En el camino, se familiarizará con los estados, cómo resolver las dependencias, y aprenderá varios métodos para la navegación.
- Entendiendo los estados
- Introducción al Router UI de Angular
- Navegación entre estados
- Descarga e instalación
- Usando Angular UI Router
- Configuración
- Definiendo estados
- Resolver datos con el servicio de artículos
- Uso de datos resueltos en el ArticlesController
- Renderización de la lista de artículos
- Más navegación abstracta
- Conclusión
Entendiendo los estados
Quizás la mejor manera de apreciar el problema que el framework UI Router espera resolver es considerar la naturaleza de la Web. En la mayoría de las aplicaciones, cuando se crea un enlace a una página, se define una ruta URL explícita. Por ejemplo, si quieres navegar a la página de Productos de tu sitio, puedes tener una URL como:
Muestra de código 1: Versión 2 Contenedor flexible
http://www.example.com/products
Hay dos problemas con esta situación. El primero es que debe recordar la ruta literal exacta a la página de Productos cada vez que establezca un enlace. Aunque el ejemplo dado aquí puede ser fácil de recordar, muchas URL del mundo real no son tan fáciles de memorizar. El siguiente problema se presenta cuando ocurre lo inevitable y alguien decide cambiar la ruta a otra cosa. Cuando una URL cambia, hay que asegurarse de que todos los enlaces existentes se actualizan para apuntar a la nueva ubicación.
En lugar de tener que seguir la pista de las rutas de la URL al pie de la letra, ¿no preferiría simplemente decirle a la aplicación que «vaya a la página de Productos»? Al permitir que la aplicación se ocupe de la navegación, usted se libra de tener que conocer la ruta literal y se protege de los enlaces rotos causados por la inevitabilidad del cambio. El uso de estados le proporciona este nivel de flexibilidad. Un estado encapsula una ubicación URL, el nombre del estado, datos especializados para la vista, identifica una forma de localizar o generar la vista, e incluso puede exponer eventos personalizados.
Introducción al Router UI de Angular
El Router UI de Angular es un framework que reemplaza completamente el enrutamiento nativo disponible en AngularJS. El UI Router es muy similar al enrutamiento nativo de AngularJS en el sentido de que las aplicaciones se componen de un shell que contiene un marcador de posición para el contenido dinámico. La Figura 1 muestra cómo el shell de la aplicación alberga un elemento que utiliza la directiva ui-view. A medida que se evalúan las reglas de estado en el framework, el contenido HTML se renderiza dentro del marcador de posición.
Angular UI Router es un framework que reemplaza completamente el enrutamiento nativo disponible en AngularJS.
Además de renderizar contenido HTML, el framework UI Router soporta el enrutamiento de URLs, la capacidad de resolver dependencias antes de que los controladores sean inicializados, vistas nombradas y anidadas, filtros de utilidad, eventos de cambio de estado y transiciones declarativas entre estados.
Hay algunas formas diferentes que puedes usar para moverte entre diferentes estados. La primera forma es la directiva ui-sref. Probablemente esté familiarizado con el atributo href de la etiqueta anchor de HTML (que representa una referencia de hipertexto); del mismo modo, la directiva ui-sref se refiere a una referencia de estado. La directiva se utiliza declarando un nombre de estado con la directiva ui-sref aplicada a un ancla. Por ejemplo:
<a ui-sref="about">About Us</a>
Cuando el marco de trabajo del enrutador de la interfaz de usuario evalúa esta directiva, el ancla se transforma para tener el valor de URL apropiado. Por ejemplo:
<a ui-sref="about" href="#about">About Us</a>
Nota que el elemento se actualiza para incluir un atributo href con un valor correspondiente a cómo debe actualizarse la URL para navegar a la página Acerca de nosotros. La directiva ui-sref es bastante flexible. Soporta escenarios simples así como formas de tratar con estados anidados e incluso valores parametrizados.
El siguiente enfoque para navegar entre estados es utilizar un método fuera del objeto $state que está disponible para un controlador de Angular. En este siguiente fragmento, puedes ver cómo se implementa el método navigate para llamar a $state.go y hacer la transición de la aplicación al estado about.
angular.module('app') .controller('PageController', );
El objeto $state es inyectado por el framework UI Router e incluye un número de métodos para ayudarte a gestionar y manipular el estado en la aplicación. El valor aquí es que le dices a la aplicación que «vaya» al estado sobre y te liberas de conocer la ruta literal de la URL a la página.
Descarga e instalación
Hay un número de diferentes maneras en que puedes obtener acceso al framework UI Router. Puedes descargar la última versión directamente desde el repositorio de GitHub en https://github.com/angular-ui/ui-router. Alternativamente, puedes instalar el framework a través de Bower o NuGet o incluso incluir enlaces CDN en tus páginas; ambos están disponibles en http://cdnjs.com/libraries/angular-ui-router.
Usando Angular UI Router
Lo que sigue es un tutorial que demuestra cómo construir una simple aplicación basada en contenido estático usando el framework UI Router. La Figura 2 muestra la aplicación de ejemplo de la página de inicio que aprenderás a construir mientras lees este artículo. En esta captura de pantalla, puedes ver el shell de la aplicación y cómo el contenido de la página de inicio se inyecta en el marcador de posición utilizando la directiva ui-view.
Puedes cambiar de estado para navegar a la página de contacto, como se muestra en la Figura 3. El mecanismo de la página de contacto utiliza el método go del objeto $state pasando un nombre de estado al método.
El siguiente estado está asociado a la página de lista de artículos, como se ve en la Figura 4. Aquí, se pone a disposición de la vista una matriz de datos de artículos después de que el UI Framework inyecte los valores brutos en el controlador. La navegación en esta página se facilita a través de la directiva ui-sref que permite expresar declarativamente el estado de la aplicación al que se quiere navegar.
La última página, ilustrada en la Figura 5, muestra cómo se utiliza un estado anidado en la aplicación.
Configuración
Para empezar a trabajar con el framework UI Router, hay que configurar la página. El primer paso es añadir el nombre de la aplicación en el atributo ng-app del elemento HTML de la página. Aquí, el nombre de la aplicación es simplemente app.
< html ng-app="app">
Luego, tienes que añadir la directiva ui-view a un elemento de la página para que actúe como marcador de posición para el contenido inyectado por el framework. En este caso, la directiva se añade a un elemento div.
< div ui-view></div>
Por último, debes hacer referencia tanto a Angular como al router Angular UI en la página.
<script src="scripts/lib/angular.min.js"></script><script src="scripts/lib/angular-ui-router.min.js"></script><script src="scripts/app/app.js"></script>
Este fragmento de código también incluye la referencia al script app.js en la carpeta script/app que contiene el código para inicializar la aplicación Angular. En el proceso de inicialización es donde se implementa la mayor parte de la configuración y la interfaz con el framework UI Router.
Definiendo estados
Como se ha dicho anteriormente, la base del framework UI Router es el uso de diferentes estados en una aplicación. Al acceder a cada uno de estos estados, la aplicación puede navegar o reconstituirse a las circunstancias dentro del ciclo de vida de la aplicación. La siguiente sección demuestra cómo definir los estados para la aplicación; todo el código se implementa en el archivo app.js. Cada sección se examina de forma aislada, pero si quieres ver el script de inicialización completo, consulta el Listado 1.
El primer paso es configurar el UI Router en tu aplicación AngularJS. Después de nombrar el módulo, tienes la oportunidad de registrar el framework UI Router como una dependencia de la aplicación añadiendo el literal ui.router en el array de dependencias. (Nótese cómo el comentario denota un marcador de posición para el código en un fragmento posterior.)
angular.module('app', ) .config(/* add configuration here */);
Una vez definido el módulo y registradas las dependencias, la aplicación se configura para ejecutar una función anónima que se ejecuta durante la fase de configuración de la aplicación. Aquí, hay unos cuantos recursos inyectados en la función que son relevantes para el framework UI Router.
El objeto $stateProvider presenta el método state que permite definir estados granulares de la aplicación que pueden coincidir o no con los cambios en la URL. El objeto $urlRouterProvider es un objeto que permite controlar cómo se gestiona y observa la ubicación del navegador. En el contexto del UI Router, $urlRouterProvider se utiliza para ayudar a definir un escenario de navegación global. Cada uno de estos objetos se discute con más detalle en los próximos fragmentos de código. (Una vez más, ten en cuenta que los siguientes fragmentos de código se colocan en la posición del comentario del marcador de posición en el fragmento anterior.)
Cada estado de la aplicación se define proporcionando un nombre y diciéndole al framework dónde encontrar el marcado para la vista. Aquí, el estado home se define proporcionando la ubicación de la raíz para la url y un valor para la propiedad templateUrl.
$stateProvider .state('home', { url: '/', templateUrl: '/partials/home.html' })
Esto le dice a la aplicación que cargue el contenido del archivo home.html en el marcador de posición ui-view cuando el usuario navega a la raíz de la aplicación. Aquí se empieza a ver una de las ventajas de tener un enrutamiento centrado en el estado. Si, por alguna razón, quisieras que la URL del estado home apuntara a /home en lugar de a la ubicación raíz (/), ese cambio sólo tendría que ocurrir aquí en la configuración. Este estado renuncia a cualquier configuración avanzada y carga una página estática en el navegador. Puede haber otras ocasiones en las que desee asociar un controlador específico con el estado.
El estado de contacto está configurado para cargar el marcado de la página contact.html en el marcador de posición ui-view. Más allá de hacer una operación básica de reemplazo, el ContactsController también se asocia a la vista con un alcance al nivel del elemento DOM que aloja la directiva ui-view.
.state('contact', { url: '/contact', templateUrl: '/partials/contact.html', controller: 'ContactController', })
Como se muestra en la Figura 3, la página de Contacto incluye un botón para navegar a la página de Artículos. La navegación se realiza en el ContactsController y demuestra cómo cablear un controlador a una vista cargada bajo demanda por el framework UI Router.
El estado de la página Article lleva la configuración un paso más allá añadiendo valores en el objeto que resuelve cualquier valor configurado definido en el objeto. El propósito de este estado es renderizar una lista de los artículos disponibles en el sitio. Este estado se configura para que la información del artículo esté disponible para el controlador antes de que se instancie. En el siguiente fragmento, el estado define un valor en el objeto resolve.
.state('articles', { url: '/articles', templateUrl: '/partials/articles.html', resolve: { articles: 'ArticlesService' }, controller: 'ArticlesController' })
En este caso, la propiedad articles apunta a la cadena ArticlesService. Cuando se pasa una cadena como valor a la propiedad resolve, el framework se pone en contacto con un servicio registrado con el mismo nombre y resuelve el servicio hasta su valor final. En este caso, el ArticlesService devuelve una promesa, por lo que el controlador asociado no se instanciará hasta que se resuelva la promesa del servicio y el objeto final esté disponible como valor inyectable para el controlador. La implementación del ArticlesService está disponible en el Listado 3.
Después de que la lista de artículos se presenta al usuario como se muestra en la Figura 4, el usuario puede seleccionar un artículo y profundizar en el contenido del sitio. Esta acción está representada por un estado anidado. Observe cómo el nombre del estado incluye un punto (.) entre articles y article para denotar una relación padre e hijo entre los estados.
.state('articles.article', { url: '/:pageName', templateUrl: function ($stateParams) { return '/partials/articles/' + $stateParams.pageName + '.html'; } });
Aquí, hay una regla especial aplicada a cómo se evalúa la propiedad url. Dado que se trata de una vista anidada (como indica el punto en el nombre del estado) el valor de la propiedad url se concatenará con el valor url del estado padre. Esto significa que cualquier estado que coincida tendrá una URL que comienza con /articles y luego incluye el nombre de la página del artículo.
La presencia de los dos puntos (:) es indicativa de un parámetro de URL. Al introducir un parámetro en la URL, la definición del estado se vuelve lo suficientemente flexible como para manejar cualquier estado que coincida con la relación que tiene con su estado padre. Este estado también cuenta con una función que se ejecuta para devolver el valor de templateUrl. El uso de una función aquí le da la oportunidad de utilizar los parámetros definidos en la url del estado. El nombre que le des al parámetro en la propiedad url coincide con el nombre de la propiedad del objeto $stateParams. Por lo tanto, este estado toma el pageName pasado en la URL para usarlo en la función templateUrl para acceder a los archivos de contenido individuales que eventualmente se inyectan en el elemento que alberga la directiva ui-view.
Este es el último estado definido en la aplicación. Para ver cómo se implementan todos los estados en el script de inicialización real, consulte el Listado 1.
La última orden que hay que dar a la aplicación es qué hacer si el usuario intenta acceder a una URL que no está definida en el método configure. Usando el método otherwise del objeto $urlRouterProvider, cualquier URL no reconocida es descartada y la aplicación es redirigida a una ubicación por defecto. En este caso, la aplicación está configurada para redirigir a la URL raíz si la URL dada no coincide con un estado definido.
$urlRouterProvider.otherwise('/');
Ahora, con cada estado de la aplicación definido, se puede empezar a prestar atención a la construcción del ArticlesService.
Resolver datos con el servicio de artículos
La configuración para el estado del artículo incluye un valor para la opción resolver. Este objeto está configurado para tener un valor de cadena de ArticlesService establecido en la propiedad articles (ver Listado 1 para el contexto). Proporcionar una cadena al objeto resolve le dice al framework que localice un servicio registrado en la aplicación y que resuelva el servicio hasta su valor final. El ArticlesService está implementado para devolver una promesa.
angular.module('app').factory('ArticlesService', ); return deferred.promise; }]);
Aquí, el servicio está usando el servicio $q para crear una promesa para devolver un array. En este caso, los valores están codificados, pero en un contexto del mundo real, puede ser necesario acceder a un servidor remoto para proporcionar los datos. En cualquier caso, el servicio debe estar completamente resuelto antes de que el marco del enrutador pase la ejecución al controlador asociado. Por lo tanto, a medida que se invoca el estado del artículo, en última instancia, al controlador se le pasa una matriz de objetos de artículo como dependencia.
Uso de datos resueltos en el ArticlesController
Una de las ventajas de utilizar el marco de trabajo del enrutador de la interfaz de usuario es la capacidad de hacer cumplir la separación de preocupaciones. Como el estado de los artículos implementa un objeto resuelto, el array de artículos en bruto se inyecta en el controlador.
angular.module('app') .controller('ArticlesController', );
Este enfoque es superior a requerir que el ArticlesController «conozca» el ArticlesService porque es mucho más fácil burlarse de un array de objetos en bruto con fines de prueba en lugar de tratar de burlarse del propio servicio. La implementación completa para los controladores de la aplicación se encuentra en el Listado 2.
Renderización de la lista de artículos
Ahora que la aplicación ha navegado al estado del artículo y el controlador tiene el array de artículos resuelto en el ámbito, la vista está lista para ser renderizada. La vista de artículos se compone de dos partes. La primera es otro marcador de posición div que utiliza la directiva ui-view para crear una vista anidada. La segunda es una lista desordenada de los diferentes artículos disponibles en el sitio. La construcción de la vista de esta manera permite hacer clic en diferentes títulos de artículos mientras la lista de artículos permanece en la página. (Puede ver un ejemplo de esto en la Figura 5.) Esto es posible porque el contenido de la página se carga en la ui-view a nivel de artículo mientras que la página en su conjunto se renderiza en la ui-view en el shell de la aplicación. La implementación completa del shell de la aplicación está disponible en el Listado 4.
El siguiente fragmento de código demuestra cómo la vista de artículos implementa una vista anidada.
<div ui-view> <!-- default content goes here --></div>...<ul class="list-group"> <li class="list-group-item" c> <a ui-sref="articles.article({pageName: '{{article.pageName}}'})"> {{article.title}}</a> </li></ul>
Hay tres formas en las que este marcado utiliza el framework UI Router. En primer lugar, el elemento div utiliza la directiva ui-view como marcador de posición y, como se indica en el comentario, se puede pasar contenido predeterminado para renderizar en el marcador de posición antes de que el marco renderice cualquier contenido. El listado 5 demuestra cómo se utiliza un mensaje estático como contenido de marcador de posición en la página antes de que se cargue cualquier contenido en la vista.
En segundo lugar, el elemento anchor tiene la directiva ui-sref aplicada. Esto señala al marco del enrutador de la interfaz de usuario para procesar este enlace en el contexto del marco y, en última instancia, rinde un valor href estándar que coincide con la URL para el estado declarado en función de los ajustes definidos en la configuración de la aplicación (véase el listado 1).
La tercera forma en que se utiliza el framework es que el valor de la directiva ui-sref acepta una expresión para generar el valor correcto de la URL para un estado anidado. Aquí, se pasa un hash a la jerarquía de estados anidados (en este caso articles.article) donde el valor de pageName está ligado al pageName del artículo entrante. Cuando el framework UI Router evalúa esta expresión, se genera un valor de URL correspondiente para cada artículo que coincida con las reglas de estado definidas.
El último controlador a implementar es el ContactController, que utiliza el método go del parámetro state para navegar la aplicación a un nuevo estado.
app.controller('ContactController', );
Aquí, simplemente llamando a go con un nombre de estado, tu controlador sólo se preocupa de declarar el estado al que desea cambiar en lugar de tratar de seguir el esquema de enrutamiento concreto en la aplicación.
Conclusión
Aunque AngularJS viene abastecido con una implementación de enrutamiento funcional, puedes darte cuenta rápidamente de las ventajas de utilizar un marco de enrutamiento basado en estados para aplicaciones no triviales. El framework UI Router proporciona formas sencillas de definir estados, resolver dependencias y hacer uso de vistas anidadas. Para obtener más información sobre lo que el marco puede hacer, asegúrese de visitar la casa del proyecto en GitHub en https://github.com/angular-ui/ui-router/.