Tabla de Contenidos

10.7 AngularJS

Ya tenemos un API REST funcionando en el servidor, así que vamos a volver a la parte de AngularJS para por fin tener una aplicación funcionando capaz de hacer un CRUD.

remoteResource

Lo primero es acabar el servicio remoteResource para que haga uso del API REST que hemos creado.El servicio se basa en la función RemoteResource que es la que tiene toda la funcionalidad , la hemos modificado para que los 2 métodos que ya hay hagan uso del API REST.

function RemoteResource($http, $q, baseUrl) {
    this.get = function(idSeguro) {
        var defered = $q.defer();
        var promise = defered.promise;

        $http({
            method: 'GET',
            url: baseUrl + '/api/SeguroMedico/' + idSeguro
        }).success(function(data, status, headers, config) {
            defered.resolve(data);
        }).error(function(data, status, headers, config) {
            if (status === 400) {
                defered.reject(data);
            } else {
                throw new Error("Fallo obtener los datos:" + status + "\n" + data);
            }
        });

        return promise;

    };

    this.list = function() {
        var defered = $q.defer();
        var promise = defered.promise;

        $http({
            method: 'GET',
            url: baseUrl + '/api/SeguroMedico'
        }).success(function(data, status, headers, config) {
            defered.resolve(data);
        }).error(function(data, status, headers, config) {
            if (status === 400) {
                defered.reject(data);
            } else {
                throw new Error("Fallo obtener los datos:" + status + "\n" + data);
            }
        });


        return promise;
    };
}

Como vemos los cambios han sido mínimos, solo la nueva URL y la gestión del error 400 Bad Request que ahora es lo que hace que se rechace la promesa.

Pasemos ahora a ver el resto de los métodos. Son casi todos iguales y solo cambia el método HTTP, la URL y los datos que se envían.

    this.insert = function(seguroMedico) {
        var defered = $q.defer();
        var promise = defered.promise;

        $http({
            method: 'POST',
            url: baseUrl + '/api/SeguroMedico',
            data: seguroMedico
        }).success(function(data, status, headers, config) {
            defered.resolve(data);
        }).error(function(data, status, headers, config) {
            if (status === 400) {
                defered.reject(data);
            } else {
                throw new Error("Fallo obtener los datos:" + status + "\n" + data);
            }
        });

        return promise;
    };

    this.update = function(idSeguro, seguroMedico) {
        var defered = $q.defer();
        var promise = defered.promise;

        $http({
            method: 'PUT',
            url: baseUrl + '/api/SeguroMedico/' + idSeguro,
            data: seguroMedico
        }).success(function(data, status, headers, config) {
            defered.resolve(data);
        }).error(function(data, status, headers, config) {
            if (status === 400) {
                defered.reject(data);
            } else {
                throw new Error("Fallo obtener los datos:" + status + "\n" + data);
            }
        });

        return promise;
    };

    this.delete = function(idSeguro) {
        var defered = $q.defer();
        var promise = defered.promise;

        $http({
            method: 'DELETE',
            url: baseUrl + '/api/SeguroMedico/' + idSeguro
        }).success(function(data, status, headers, config) {
            defered.resolve(data);
        }).error(function(data, status, headers, config) {
            if (status === 400) {
                defered.reject(data);
            } else {
                throw new Error("Fallo obtener los datos:" + status + "\n" + data);
            }
        });

        return promise;
    };

Esta clase con poco de trabajo la podríamos hacer genérica y poder reusarla en otros proyectos , sin embargo en vez de usar esta clase existen en AngularJS dos alternativas a crearte tu propia clase:

Aunque nuestra clase funcione perfectamente, estas 2 últimas disponen de muchas mas funcionalidades con lo que deberías usarse en proyectos reales y decir que el equipo de AngularJS está trabajando en mejorar aun mas su clase $resource.

Aun así nuestra clase remoteResource ha cumplido perfectamente su papel para entender como funciona todo.

La aplicación

Como ya tenemos acabado el servicio remoteResource ya podemos modificar la aplicación para poder hacer las siguientes operaciones:

Borrar seguro médico

Para borrar un seguro médico vamos a añadir un botón de borrado en la tabla en la que se muestran todos los seguros médicos. Estebotón llamará a una función del $scope que es la que realizará la tarea.

<table>
  <thead>
    <tr>
      <th>NIF</th>
      <th>Nombre</th>
      <th>Apellido</th>
      <th>Sexo</th>
      <th>Accion</th>
    </tr>
  </thead>
  <tfoot>
    <tr>
      <td colspan="3">El Nº de seguros medicos es:</td>
      <td ng-bind="seguros.length"></td>
    </tr>
  </tfoot>
  <tbody>
    <tr ng-repeat="seguro in seguros| filteri18n:{ape1:filtro.ape1}" ng-style="{color:($odd?'red':'green')}">
      <td><a ng-href="#/seguro/edit/{{seguro.idSeguro}}">{{seguro.nif}}</a></td>
      <td>{{seguro.nombre}}</td>
      <td>{{seguro.ape1}}</td>
      <td ng-switch on="seguro.sexo">
        <span ng-switch-when="H">Hombre</span>
        <span ng-switch-when="M">Mujer</span>
        <span ng-switch-when=""></span>
        <span ng-switch-default>Desconocido</span>
      </td>
      <td>
           <button ng-click="borrar(seguro.idSeguro)">Borrar</button>
      </td>
    </tr>
  </tbody>
</table>

El controlador ListadoSeguroController con el nuevo método borrar(idSeguro) queda de la siguiente forma:

app.controller("ListadoSeguroController", ['$scope', 'seguros', 'remoteResource', function($scope, seguros, remoteResource) {
        $scope.seguros = seguros;

        $scope.borrar = function(idSeguro) {
            remoteResource.delete(idSeguro).then(function() {
                remoteResource.list().then(function(seguros) {
                    $scope.seguros = seguros;
                });
            });
        };

    }]);

Actualizar los datos de un seguro médico

Vamos ahora actualizar los datos de un seguro médico que ya existe. Lo primera que vamos a hacer es cambiar el nombre del controlador DetalleSeguroController a EditSeguroController ya que así queda mas claro que vamos a editar los datos del seguro médico.

Lo único que vamos a hacer es modificar la función guardar para que finalmente llame a la base de datos.

app.controller("EditSeguroController", ['$scope', 'seguro', 'remoteResource', '$location', function($scope, seguro, remoteResource, $location) {

        $scope.filtro = {
            ape1: ""
        };

        $scope.sexos = [{
                codSexo: "H",
                descripcion: "Hombre"
            }, {
                codSexo: "M",
                descripcion: "Mujer"
            }];



        $scope.seguro = seguro;

        $scope.guardar = function() {
            if ($scope.form.$valid) {
                remoteResource.update($scope.seguro.idSeguro, $scope.seguro).then(function() {
                    $location.path("/seguro/listado");
                });
            } else {
                alert("Hay datos inválidos");
            }
        };

    }]);

Añadir un nuevo seguro médico

Para añadir un nuevo seguro es necesario añadir una nueva ruta y crear un nuevo controlador.

El path de la nueva ruta será /seguro/new y el controlador se llamará NewSeguroController. Añadiremos el siguiente código debajo de las rutas que habíamos definido en el bloque de configuración:

        $routeProvider.when('/seguro/new', {
            templateUrl: "detalle.html",
            controller: "NewSeguroController"
        });

El controlador quedará de la siguiente forma:

app.controller("NewSeguroController", ['$scope', 'remoteResource', '$location', function($scope, remoteResource, $location) {

        $scope.filtro = {
            ape1: ""
        };

        $scope.sexos = [{
                codSexo: "H",
                descripcion: "Hombre"
            }, {
                codSexo: "M",
                descripcion: "Mujer"
            }];


        $scope.seguro = {
            nif: "",
            nombre: "",
            ape1: "",
            edad: undefined,
            sexo: "",
            casado: false,
            numHijos: undefined,
            embarazada: false,
            coberturas: {
                oftalmologia: false,
                dental: false,
                fecundacionInVitro: false
            },
            enfermedades: {
                corazon: false,
                estomacal: false,
                rinyones: false,
                alergia: false,
                nombreAlergia: ""
            },
            fechaCreacion: new Date()
        };


        $scope.guardar = function() {
            if ($scope.form.$valid) {
                remoteResource.insert($scope.seguro).then(function() {
                    $location.path("/seguro/listado");
                });
            } else {
                alert("Hay datos inválidos");
            }
        };

    }]);

Para acceder a esta nueva página 1) añadimos un nuevo link en la en la página del listado de seguros médicos, en listado.html

<a href="#/seguro/new">Nuevo Seguro Medico</a>

Recuerda que al usar los enlaces de HTML se debe poner la almohadilla ”#” mientras que usando el servicio $location no es necesario.

Si ejecutamos la aplicación e insertamos un nuevo seguro médico se producirá la excepción:

com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "fechaCreacion" 

Esto es debido a que aun no estamos tratando la propiedad “fechaCreacion” para solucionarlo debemos indicar a la librería Jackson que no pasa nada si falta algún campo. Ello se hace mediante la línea:

objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

Así que el método fromJson de la clase JsonTransformerImplJackson quedará de la siguiente forma:

    @Override
    public Object fromJson(String json, Class clazz) {
        try {
            ObjectMapper objectMapper = new ObjectMapper();
            objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
                        
            return objectMapper.readValue(json, clazz);
        } catch (IOException ex) {
            throw new RuntimeException(ex);
        }
    }

Gestión de errores

Ahora nos falta hacer la gestión de errores que nos retorna el servidor. Como sabemos el servidor puede retorna un 400 Bad Request si la petición no es correcta y en ese caso nos retorna un array de ojetos BussinessMessage. Ya hemos modificado el servicio remoteResource para que solo en el caso de retornar un 400 Bad Request se rechace la promesa y nos revuelva el array de ojetos BussinessMessage.

Ahora tenemos que modificar los controladores para que en caso de rechazo de la promesa nos guardemos el array de ojetos BussinessMessage en la propiedad bussinessMessages del $scope del controlador. Hacemos esto porque de esa forma , al estar en el $scope, podremos mostrar el array en la página HTML.

¿que cambios tenemos que hacer? Simplemente en todos los then de las promesas , añadir como segundo parámetro la siguiente función:

function(bussinessMessages) {
  $scope.bussinessMessages = bussinessMessages;
}

Entonces las llamadas a remoteResource quedan de la siguiente forma:

Al insertar:

        $scope.guardar = function() {
            if ($scope.form.$valid) {
                remoteResource.insert($scope.seguro).then(function() {
                    $location.path("/seguro/listado");
                }, function(bussinessMessages) {
                    $scope.bussinessMessages = bussinessMessages;
                });
            } else {
                alert("Hay datos inválidos");
            }
        };

Al actualizar:

        $scope.guardar = function() {
            if ($scope.form.$valid) {
                remoteResource.update($scope.seguro.idSeguro, $scope.seguro).then(function() {
                    $location.path("/seguro/listado");
                }, function(bussinessMessages) {
                    $scope.bussinessMessages = bussinessMessages;
                });
            } else {
                alert("Hay datos inválidos");
            }
        };

Al borrar:

        $scope.borrar = function(idSeguro) {
            remoteResource.delete(idSeguro).then(function() {
                remoteResource.list().then(function(seguros) {
                    $scope.seguros = seguros;
                }, function(bussinessMessages) {
                    $scope.bussinessMessages = bussinessMessages;
                });
            }, function(bussinessMessages) {
                $scope.bussinessMessages = bussinessMessages;
            });
        };

Como acabamos de explicar simplemente es poner la función como segundo parámetro del then ya que es la que se ejecutará si la promesa se rechaza, cosa que solo ocurrirá si el servidor retorna un estado 400 Bad Request.

Como ya comentamos en el_uso_desde_el_controlador no vamos a tratar si falla la llamada a remoteResource.list() en el resolve.

Mostrar los mensajes

Por último nos queda mostrar los mensajes en la pantalla. Aparentemente es sencillo ya que solo tenemos que mostrar una lista de objetos por pantalla, pero debe cumplir las siguiente condiciones:

La solución a ésto es el siguiente código HTML:

<div ng-show="bussinessMessages.length > 0">
    <div>Se han producido los siguientes errores</div>
    <ul>
        <li ng-repeat="bussinessMessage in bussinessMessages">
            <strong ng-hide="businessMessage.fieldName == null">{{bussinessMessage.fieldName}}:</strong>
            {{bussinessMessage.message}}.
        </li>
    </ul>
    <button ng-click="bussinessMessages = []" >Quitar mensajes</button>
</div>

Si ves el resultado del HTML te puede parecer muy cutre pero por ejemplo con Bootstrap y simplemente usando Dismissible alerts puede quedar tan elegante como en la siguiente imagen:

Y quitamos el horrible botón de “Quitar mensajes” substituyendolo por el aspa de la parte superior derecha.

Ejemplo

El ejemplo de esta unidad es exactamente lo que acabamos de contar pero en un nuevo proyecto llamado “segurosv1”.

Para poder probar los mensajes que llegan del servidor mediante un error 400 Bar Request hemos modificado la clase SeguroMedicoDAOImplJDBC para incluir 2 fallos, que son:

Por supuesto estas restricciones no tienen ningún sentido y están puestas por un motivo puramente pedagógico.

Al principio del método insert se han añadido las siguientes líneas:

        if ("11111111A".equals(seguroMedico.getNif())) {
            throw new BussinessException(new BussinessMessage("nif","La letra no es válida"));
        }

Al principio del método delete se han añadido las siguientes líneas:

        if (idSeguroMedico==1) {
            throw new BussinessException(new BussinessMessage(null,"No es posible borrar el seguro médico"));
        }

Referencias

1) realmente a la ruta