6. Cliente AngularJS
En esta sesión vamos a hacer uso de los servicios REST implementados y desplegados en OpenShift, y vamos a crear una interfaz realizada en HTML5 + Bootstrap + AngularJS para consumir dichos servicios a través de una aplicación web SPA.
Dado que estamos trabajando con clientes ricos y aplicaciones desacopladas, nuestra aplicación web se desarrollará en un proyecto independiente del proyecto java, y también se ejecutará en otro servidor.
Las ventajas de esta arquitectura son múltiples. Al tratarse nuestra aplicación web de un consumidor de servicios, sería muy fácil crear una aplicación móvil con Apache Cordova, o una aplicación de escritorio con Electron. Simplemente habría que introducir el código del ejercicio en la carpeta webapps de cada plataforma.
Mediante el uso de Bootstrap, estamos garantizando un diseño responsive que se adapta de manera adecuada a todo tipo de dispositivos. Aunque la solución idónea para dispositivos móviles pasa por Ionic Framework: un framework basado en AngularJS y con ui-router como gestor de rutas que se ha convertido en el framework de referencia a la hora de desarrollar aplicaciones móviles híbridas.
Aunque la aplicación es responsive, la maquetación llevada a cabo no se adapta del todo bien a dispositivos móviles. Pese a que muchas aplicaciones se puedan adaptar correctamente, en mi opinión los dispositivos móviles suelen utilizar otros patrones y el desarrollo de la interfaz no debería ser adaptable, sino específico para éstos. Es por ello que considere a ionic framework algo imprescindible para las interfaces de aplicaciones móviles, pudiendo reaprovechar lógica de negocio existente en servicios, filtros y algunas directivas. |
6.1. Fork del repositorio
Empezaremos realizando un fork del repositorio https://bitbucket.org/java_ua/jbib-angular-expertojava. Éste tiene una base de código sobre la que empezaremos.
6.2. Instalación de las dependencias del proyecto
Fijémonos en los ficheros .bowerrc
y package.json
. En éstos se definen las dependencias de nuestro proyecto, de igual
manera que maven lo hace con los paquetes java que podamos necesitar. El hecho de usar un gestor de dependencias hace
que el tamaño de nuestro repositorio sea mucho más liviano, que podamos ver las versiones de nuestras dependencias de una
manera sencilla (el fichero indica la versión), y que todos los desarrolladores puedan trabajar con las mismas versiones
de las librerías de las que depende un proyecto.
Para instalarnos las dependencias, ejecutaremos, desde la línea de comandos y poniéndonos en la raíz de nuestro proyecto:
$ npm install
Con esto habremos instalado las librerías que necesitamos para automatizar tareas. Ahora, para instalar las dependencias de nuestra aplicación web, ejecutaremos el comando:
$ bower install
Ya tenemos instaladas todas las dependencias. El gestor también nos habrá instalado un pequeño servidor web, que utilizaremos para probar nuestra aplicación. Podemos lanzarlo mediante el comando:
$ node node_modules/webserver/webserver.js
Si todo va bien, deberíamos obtener la salida:
WEBROOT.
Listening @ 8003
Así, ya tenemos un servidor corriendo y escuchando en el puerto 8003, y otro servidor en OpenShift con nuestra API. También podría ser un Wildfly corriendo de manera local en nuestra máquina, pero con la primera opción tenemos más presente el desacoplamiento. El ejercicio se corregirá probándolo contra vuestra API de OpenShift y toda la configuración deberá dejarse preparada de esta manera.
6.3. CORS en clientes web
Para que nuestra aplicación pueda realizar peticiones AJAX contra un servidor remoto, deberemos crear un filtro para que la aplicación soporte CORS (Cross-Origin Resource Sharing) que nos permitirá saltarnos el sandbox de las peticiones AJAX, que en un principio sólo permiten realizarse dentro del mismo dominio. También, vamos a tener que realizar alguna modificación más sobre nuestro código Java.
Esto se debe a que muchas peticiones requieren una comunicación extra entre el cliente y el servidor antes de realizar
una petición GET
, POST
, PUT
o DELETE
. A esto se le conoce como preflight request, y es una llamada al servidor,
sin payload, y que se envía con una cabecera OPTIONS
.
Es por ello que debemos preparar nuestro código Java para que acepte este tipo de llamadas. Esto podríamos resolverlo de dos maneras:
-
Creando una función para cada llamada con la anotación
@OPTIONS
y devuelva un código de respuesta200 OK
. -
Creando un filtro que devuelva una respuesta
200 OK
en cada petición con cabeceraOPTIONS
directamente desde el filtro, sin llegar a pasar por la lógica de negocio.
Para el ejercicio, se ha optado por la primera opción, al considerarla más restrictiva.
Un ejemplo concreto, para la clase LibroResource, sería el siguiente:
@OPTIONS
@Consumes({"application/json"})
@Produces({"application/json"})
@Path("{id}/prestamos")
public Response realizarPrestamoOptions(@PathParam("id") Long id) {
return Response.ok().build();
}
Antes de realizar un préstamo, se enviará una petición sin payload al servidor y con la cabecera OPTIONS
.
Si la respuesta es correcta, será entonces cuando se envíe la petición POST
.
Para aliviar la tarea de implementar este nuevo código, se adjunta el filtro CORS, así versiones modificadas de las clases de recursos con estas anotaciones añadidas:
Si tu solución difiere de la que se ha adjuntado, deberás adaptar el código para que funcione correctamente. |
6.4. Aplicación web
Como hemos dicho, la aplicación web estará desarrollada en AngularJS, y hará uso de los servicios REST implementados.
El código de la aplicación (javascript y HTML) está incompleto, y tendremos que terminarlo para que ésta funcione.
Se ha puesto un TODO
allá donde haga falta realizar algo. IntelliJ Idea tiene una vista de TO-DOs,
donde podemos verlos todos juntos para que no se nos pase ninguno:

La funcionalidad se corresponderá con las acciones asocidadas a un usuario, y pasamos a ver en los siguientes pantallazos:
6.4.1. Login
Será el punto de entrada a la aplicación, y desde la que el usuario introducirá sus credenciales de acceso:

Si las credenciales introducidas son incorrectas, se indicará al usuario mediante un mensaje:

El sistema de autenticación
6.4.2. Listado de libros
Inmediatamente después de acceder a la aplicación, veremos el listado de libros:

Cada libro es una directiva que tendremos que terminar de implementar. Además, como el serivcio REST nos devuelve la miniatura de la imagen, vamos a implementar un filtro que modifique la cadena para que devuelva la imagen a tamaño completo. No queremos modificar el servicio, sino lo que éste nos devuelve y por eso lo vamos a hacer desde el lado del cliente.
6.4.3. Detalle de un libro
Muestra el detalle de un libro: título, autor, isbn, número de ejemplares total y número de ejemplares disponibles.
Además, habrá un botón Volver que hará un history.back()
del navegador y un botón Reservar, que invocará al
servicio de reservas.

Si se puede realizar la reserva de manera correcta, se mostrará una alerta en verde. Si por el contrario no se puede
realizar la reserva, se mostrará un mensaje en rojo y el motivo (por ejemplo: usuario no válido porque tiene una multa).
El fichero UsuarioResource ya captura excepciones y manda un mensaje de error junto con una cabecera 403 (Forbidden
)
en caso de no poder realizar una reserva.
El detalle del libro es una directiva. Ésta muestra en rojo el número de ejemplares disponibles cuando es cero. Además,
el botón de reservas deberá quedar deshabilitado si el número de ejemplares disponibles es cero. Si se está haciendo una
reserva, el botón de reservar también se deshabilitará para evitar el doble click, y mostrará un spinner.
La clase CSS de la rueda girando está preparada y se llama glyphicon-cog
.
A continuación del detalle del libro tenemos la lista de libros relacionados. Deberá ser un máximo de 8, y reaprovecharemos la misma directiva empleada en el listado principal de libros.
6.4.4. Listado de préstamos
Mostrará todos los préstamos del usuario:

Aunque están marcados en azul sólo los títulos de los libros, haciendo click en cualquier lugar de la fila seremos llevados al detalle del libro.
Si la fecha de devolución de un préstamo ha vencido, el texto tendrá la clase text-alert
para mostrarse en rojo.
En caso de no tener ningún préstamo, en lugar de mostrar la tabla, se enseñará un mensaje informativo:

Habrá que intentar que la vista no haga flickering, mostrando una cosa un milisegundo y luego mostrando otra.
6.4.5. Devoluciones.
La funcionalidad de devolución queda fuera del alcance este proyecto. En caso de necesitar hacer una devolución lo haremos a través de la aplicación Postman, o cualquier otro cliente REST. También podríamos acceder directamente a la base de datos y lanzar la query.
6.5. Control de acceso
Se ha implementado un sencillo control de acceso en la aplicación. Lo podemos ver en el fichero jbib.js
, en el método
run
de la aplicación. Básicamente consiste en que si no estamos en los states
de nombre login
o logout
se mirará
si el usuario está logado. Si no lo está, redirigirá a la pantalla de login.
6.6. Comunicación con el servidor
Toda la comunicación con el servidor se hará con el módulo ngResource
, que
hemos visto que facilita la integración con servicios REST.
Deberemos terminar la implementación de los siguientes servicios, que son los que favorecerán dicha comunicación:
-
booksResource.js
-
borrowService.js
-
relatedBooksResource.js
Observad en el código que borrowsService
se ha concebido de manera ligeramente distinta: mientras los otros dos
devuelven el propio recurso, éste implementa una factoría como las que hemos visto en clase.
6.7. Estructura del proyecto
Se ha tenido en cuenta algunas de las best practices a la hora de elaborar la estructura del proyecto. Cada funcionalidad está en una carpeta distinta (préstamos, libros, acceso, dashboard). Dentro de cada una de ellas, habrá una carpeta para servicios, directivas, controladores, filtros y vistas.
Las funcionalidades de login y gestión de acceso se han creado como módulos independientes, con la idea de poder ser reutilizados en otros proyectos.
.
├── books
│ ├── controllers
│ │ ├── bookController.js
│ │ └── booksController.js
│ ├── directives
│ │ ├── book
│ │ │ ├── book.html
│ │ │ └── book.js
│ │ └── book-detail
│ │ ├── book-detail.html
│ │ └── bookDetail.js
│ ├── filters
│ │ └── largeImg.js
│ ├── services
│ │ ├── booksResource.js
│ │ ├── borrowService.js
│ │ └── relatedBooksResource.js
│ └── views
│ ├── book.html
│ └── books.html
├── borrows
│ ├── controllers
│ │ └── borrowsController.js
│ └── views
│ └── borrows.html
├── bower_components
├── bower.json
├── components
│ ├── auth
│ │ ├── authInterceptor.js
│ │ ├── auth.module.js
│ │ └── authService.js
│ └── images
│ └── nophoto.png
├── dashboard
│ ├── directives
│ │ ├── navbar
│ │ │ ├── navbar.html
│ │ │ └── navbar.js
│ │ └── sidebar
│ │ ├── sidebar.html
│ │ └── sidebar.js
│ └── views
│ └── dashboard.html
├── dist
├── Gruntfile.js
├── index.html
├── jbib.css
├── jbib.js
├── login
│ ├── directives
│ │ ├── login-form.html
│ │ └── loginForm.js
│ ├── login.module.js
│ └── views
│ └── login.html
├── node_modules
├── package.json
├── tree.txt
└── tsd.json
6.8. Automatización
Se han creado unas reglas de linting y minificación. En las etapas iniciales del desarrollo será complicado de usar, pero a medida que vayamos avanzando será necesario. La prueba de la aplicación se hará sobre el código minificado, y éste no se minificará si no pasa el linting.
Lanzado el siguiente comando desde la raíz del proyecto:
$ grunt
Se ejecutará un listener que intentará pasar las reglas de JSHint y minificar el código cada vez que realizamos una modificación en el mismo. Así, sólo tendremos que refrescar el navegador cuando estas tareas se hayan realizado.
Si sólo queremos minificar el código sin que se ejecute ningún listener, haremos uso del comando:
$ grunt dist
La carpeta |
6.9. Corrección
Aplica el tag FINAL
al commit que quieras que se te corrija.