martes, 2 de diciembre de 2014

AngularJS rest CRUD $resource vs $http, ejemplo práctico implementado con $resource.

Buenas a todos, hoy veremos cómo realizar un acceso a datos típico CRUD en angular.

Angular como todo framework javascript moderno ya proporciona mecanismos que hace sencilla la integración contra un web api. Al plantearme cómo hacerlo en mis proyectos siempre tenía la duda si crearme servicios inyectando $http e implementar las acciones en cada servicio o bien usar directamente $resource ya que evitas trabajo y simplificas código (resource de base ya implementa un CRUD contra una api rest). Cabe destacar que no siempre tendrás un api rest pura en servidor o tal vez necesitas un control especial de la petición y respuesta donde tal vez sí que tendría alguna ventaja usar $http frente a $resource.

En este artículo usamos $resource porque proporciona una abstracción un nivel por encima de $http, es decir $resource se vale internamente del objeto $http y ya implementa como base un CRUD. Puedes extender la base, el ejemplo más usado es cambiar el verbo del método update para que vaya por PUT. También trabaja internamente $q (promises en angular), por tanto podemos usar promises siempre que hagamos una operación contra servidor.

Vamos al lío, si no dispones de una api contra la que testear este ejemplo, podéis usar alguna pública como esta que os servirá para testear los get. Asumo que tenéis funcionando una aplicación angular con lo básico.

En primer lugar veremos cómo configurar ng-resource:

Tendrás que descargar el paquete e inyectar la dependencia a ng-resource. Puedes obtenerlo aquí.

Ya sabréis que no es recomendable ni óptimo por diversos motivos trabajar directamente sobre el core module, aunque es posible... aquí para simplificar el tutorial inyectamos directamente en el principal.
    //AngularJS
    var app = angular.module('app', [
         //modules 
            'ngResource'
    ]);
El siguiente paso sería crear un nuevo $resource:

Decido crearme un archivo angular.config.resources.js que contendrá toda la definición de recursos que iré necesitando. Podrías optar por organizar el código diferente, tal vez no interese tener un único archivo sino que quieras tener un archivo diferente por recurso… en definitiva para gustos colores.
(function () {
    'use strict';

    var app = angular.module("app");
    // users Resource
    app.factory("UserResource", function ($resource) {
        return $resource(
            "http://jsonplaceholder.typicode.com/users/:Id",
            { Id: "@Id" },
            {
                "update": { method: "PUT" }
            }
        );
    });

//… más resources …

}());
En este bloque de código es importante asegurarse que estás añadiendo los servicios en el module que toca (el que hayas inyectado la dependencia de ng-resource en este caso ‘app’).

Si os fijáis al definir el resource estamos pasándole la url con los posibles queryString de uri que acepta con el prefijo “:” delante, en el segundo parámetro los mapeamos a variables y el tercer parámetro permite modificar el comportamiento de alguno de los métodos base o bien definir nuevos métodos. En este ejemplo hemos especificado que la acción update irá por verbo PUT.

Podéis customizar prácticamente cualquier aspecto de la petición http echarle un ojo a la doc!

Finalmente veremos cómo realizar una llamada al servicio mediante el recurso que acabamos de crearnos.
(function () {

var app = angular.module('app');
app.controller('userController', ['$scope', '$routeParams', 'UserResource', function ($scope, $routeParams, userResource,) { 
        var currentId = $routeParams.id;
        
        $scope.setDinamicContentLoading(true);
        if (currentId) {
            userResource.get({}, { 'Id': currentId }).$promise.then(function (user) {
                $scope.setDinamicContentLoading(false);
                $scope.user = user;
            }, function (error) {
                $scope.setDinamicContentLoading(false);
                alert('Error retrieving user item: ' + error);
            });
        } else {
            userResource.query().$promise.then(function (users) {
                $scope.setDinamicContentLoading(false);
                $scope.users = users;
            }, function (error) {
                $scope.setDinamicContentLoading(false);
                alert('Error retrieving user list: ' + error);
            });
        }
}());
Esta sería la definición de mi controlador para las vistas de usuario, en mi caso uso el mismo controlador para la lista de usuarios que para la vista edición de un usuario concreto. Comentar que no es recomendable añadir controladores directamente al módulo principal de angular, aquí trabajo de este modo por seguir la coherencia del primer paso donde ya hemos inyectado la dependencia en el principal por simplificar el ejemplo.

Si os fijáis, en función de si la ruta contiene un id o no, sé que estoy en una vista u otra y por tanto debería llamar a una acción u otra del servicio.

He visto bastantes tutoriales que ofrecen ejemplos donde no usan promises… úsalos!

Una vez resuelta la llamada asigno el resultado que viene de server al modelo user o users del scope y así ya los tengo disponibles en vista.

Para hacer un Create, Update y Delete sería llamar a userResource.Save(modelo) y ya tendríamos el create y así respectivamente... en la doc está bien explicado.

El tema de setDinamicContentLoading lo uso para el típico loading, comento un poco por encima como lo tengo implementado por si a alguien le interesase la idea. Trabajo con un controlador de layout que gestiona la vista principal (header, mainContent , footer,).. en mainContent cargará la vista que toque según la ruta que vayas navegando con el menú o como quiera que hayas montado la navegación.. Pues bien se trataría de añadir esto a la vista principal dentro del ámbito del layoutController donde "Ajax-loader.gif" es el típico gif animado que puedes personalizarte en internet.
<div ng-show="loading" class="dinamicContentLoading">
   <img src="./img/ajax-loader.gif" style="position:absolute;top:40%;left:45%" />
</div>
La clase css (añadimos comportamiento modal a la capa donde está contenido el gif animado):
.dinamicContentLoading {
    z-index:100;
    border : 1px solid #c0c0c0;
    background:#f0f0f0;
    padding: 0px 10px 10px 10px;
    position:fixed;
    top:0px;
    left:0px;
    height:100%;
    width:100%;
    opacity:0.7;
    filter:alpha(opacity=70);
}
layoutController.js:
  $scope.setDinamicContentLoading = function (value) {
    $scope.loading = value;
  }
Es importante entender que podemos “llamar” al método $scope.setDinamicContentLoading desde el controlador de usuario debido a Angular los contextos están comunicados jerárquicamente lo cual una vez se entiende es bastante potente (…) Lo que hacemos aquí no tiene demasiado misterio ya que es ng-show el que en función de variable $scope.loading mostrará el modal de cargando o no lo mostrará.

Hasta aquí el post de hoy, cualquier duda o discrepancia no te cortes en comentarla a través del blog o si nos sigues en redes sociales. Puedes visitar el archivo de areaTIC tal vez encuentres algún artículo que pueda interesarte.