Cuarta Parte: Elaborando el Cliente en AngularJS
Introducción
En la presente serie de artículos se ha hecho una recopilación de recomendaciones mostrando un modelo de desarrollo a través de una estructura de capas que incluye el uso de Entity Framework 7 Beta 5 con el uso de Clases POCO, DTOs, Repositorios, exposición de funciones a través de un Servicio Web Api y finalmente un cliente Java Script con AngularJS.
Por lo tanto los artículos a incluir en esta serie son los siguientes:
- Primera Parte: Creando el Modelo de Datos y DTOs
- Segunda Parte: Creando el Contexto y los Repositorios.
- Tercera Parte: Creando el Servicio Web API.
- Cuarta Parte: Elaborando el Cliente en AngularJS [Este Artículo]
El proyecto se encuentra publicado Aquí.
Acerca del Proyecto en AngularJS
Con respecto a este último Post, solo se procederá a describir las partes que se consideran más importantes del código, a fin de facilitar su entendimiento, por lo demás esté ha sido publicado en su totalidad a fin de que puedan analizarlo y modificarlo según su criterio. También cabe señalar que este se ha manejado como un Proyecto Web independiente de los Posts anteriores, cuya estructura ha quedado definida de la siguiente manera:
Configurando la Aplicación
En principio se agregó un elemento denominado Index.html, donde se hace referencia a los CDN de AngularJS; que en nuestro ejemplo han sido agregados en el body del documento:
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.15/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.15/angular-route.min.js"></script>
Luego procedimos a modificar la etiqueta html del documento insertando la directiva ng-app y colocando un identificador; en nuestro caso webApp, que definirá el ámbito de nuestra aplicación AngularJS :
<html ng-app="webApp" ng-class="{'show': loaded}" ng-init="loaded=true">
Dentro de nuestro documento Index.html, que se erige como el marco principal de esta aplicación, es necesario definir un área donde se inyectarán las sucesivas vistas que componen este proyecto, para tal efecto es necesario usar la directiva ng-view que es el complemento que sirve de enlace para procesar las rutas que serán definidas más adelante.
<div class="container">
<div class="ng-view">
</div>
</div>
Definiendo la Aplicación AngularJS
Es necesario crear el directorio App y agregar el documento javascript webApp.js, de aquí en adelante todo documento javascript será inicializado con la directiva use strict, la cual tiene por finalidad forzar el uso de variables declaradas, esto con el objetivo de identificar de manera oportuna errores de tipos no declarados en el proyecto.
"use strict";
var webApp = angular.module('webApp', ['ngRoute', 'ui.bootstrap', 'angularUtils.directives.dirPagination', 'angular-loading-bar']);
webApp.constant("confServer", {
"apiUrl": "http://localhost:59827/"
});
webApp.factory("sharedData", function () { return { value: 0 } });
Como se puede apreciar existen tres bloques de código, el primero define un modulo, el cual es el que constituye el punto de inicio de nuestra aplicacion AngularJS, además se agregan ngRoute, ui.bootstrap, etc. que son modulos adicionales que son inyectados dentro de la aplicación, a fin de que puedan ser usados mas adelante ya sea por los servicios o controllers que componen este proyecto. El segundo bloque corresponde a la definición de una constante, en este caso corresponde a la url donde estarán publicados los servicios Web API que deseamos acceder. Finalmente el tercer bloque es una rutina que nos servira para transferir data entre controladores.
Configurando las rutas del lado del Cliente
AngularJS requiere la configuración de una serie de rutas que hacen posible la navegación dentro del aplicativo, inyectando las vistas requeridas según la ruta solicitada, por ello es necesario agregar un documento javascript y denominarlo clientRoute.js, el cual tendrá la estructura tal como se muestra a continuación.
webApp.config(["$routeProvider", "$locationProvider", "$httpProvider", function ($routeProvider, $locationProvider, $httpProvider) {
$routeProvider.when("/", {
templateUrl: "/app/home/html/home.html"
});
$routeProvider.when("/unidades", {
templateUrl: "/app/unidad/html/unidadList.html",
controller: "unidadController",
controllerAs: "vm"
});
$routeProvider.when("/unidades/unidad", {
templateUrl: "/app/unidad/html/unidadForm.html",
controller: "crudUnidadController",
controllerAs: "vm"
});
$routeProvider.when("/unidades/deleteunidad", {
templateUrl: "/app/unidad/html/deleteUnidadForm.html",
controller: "crudUnidadController",
controllerAs: "vm"
});
$routeProvider.when("/productos", {
templateUrl: "/app/producto/html/productoList.html",
controller: "productoController",
controllerAs: "vm"
});
$routeProvider.when("/productos/producto", {
templateUrl: "/app/producto/html/productoForm.html",
controller: "crudProductoController",
controllerAs: "vm"
});
$routeProvider.when("/productos/deleteproducto", {
templateUrl: "/app/producto/html/deleteProductoForm.html",
controller: "crudProductoController",
controllerAs: "vm"
});
$routeProvider.when("/about", {
templateUrl: "/app/home/html/about.html"
});
$routeProvider.otherwise({
redirecTo: '/'
});
$locationProvider.html5Mode(true).hashPrefix('!');
}]);
El código mostrado presenta un patrón similar donde apreciamos una ruta consultada en el $routerProvider a traves del método when, el cual en caso de cumplirse la condición define la plantilla a ser inyectada , y según sea el caso incluye el Controller que condiciona su funcionamiento.
Definiendo los Controllers
Los controllers definen el comportamiento de la plantilla que estamos utilizando, estos se encuentran limitados según un determinado ámbito, en este caso la propia plantilla, a continuación observamos el Controller que corresponde a las funciones CRUD de Producto, el cual se denomina crudProductoController, el cual cuenta en su interior con tres funciones Save, Cancel y Destroy, cada una de ellas hace uso de un servicio que se conecta a nuestro Web API: productoService, adicionalmente existe una llamada directa a GetUnidades que también es parte del mismo servicio, el cual tiene por finalidad cargar la lista de unidades que poblaran el combobox que permite seleccionar la unidad que corresponde al producto.
webApp.controller("crudProductoController", function ($location, sharedData, productoService) {
var vm = this;
var unidades = [];
if (sharedData.value > 0) {
productoService.getProducto(sharedData.value).then(function (results) {
vm.producto = results.data;
},
function (error) {
alert(error.data.message);
});
}
else {
vm.producto = {};
vm.producto.id = 0;
}
function save() {
var Producto = {
Id: vm.producto.id,
Nombre: vm.producto.nombre,
UnidadId: vm.producto.unidadID,
Precio: vm.producto.precio
};
var promise;
if (sharedData.value > 0)
promise = productoService.put(sharedData.value, Producto);
else
promise = productoService.post(Producto);
promise.then(function (pl) {
$location.path("/productos");
},
function (errorPl) {
vm.error = 'Falló al grabar el Producto.', errorPl;
});
};
function destroy() {
var promise = productoService.delete(sharedData.value);
promise.then(function (pl) {
$location.path("/productos");
},
function (errorPl) {
$scope.error = 'Falló en la eliminación del Producto', errorPl;
});
};
productoService.getUnidades()
.then(function (results) {
vm.unidades = results.data;
},
function (error) {
alert(error.data.message);
});
function cancel() {
$location.path("/productos");
};
vm.cancel = cancel;
vm.save = save;
vm.destroy = destroy;
});
Adicionalmente para el manejo de los productos, se requiere un controller más el cual es denominado productoController, que será responsable de listar lo productos existentes de forma paginada, quién cuenta a su vez con tres funciones definidas edit, destroy y getResultsPage, el primero gestiona la adición o edición de un producto, mientras que el segundo las peticiones de eliminación, finalmente el tercero tiene por finalidad controlar el grupo de productos a mostrar según sea solicitado.
webApp.controller('productoController', function ($location, sharedData, productoService) {
var vm = this;
vm.productos = [];
vm.totalUsers = 0;
vm.usersPerPage = 10;
getResultsPage(1);
function edit(Id) {
sharedData.value = Id;
$location.path("/productos/producto");
}
function destroy(Id) {
sharedData.value = Id;
$location.path("/productos/deleteproducto");
}
function getResultsPage(newPage) {
productoService.getPaginado(newPage).then(function (results) {
vm.productos = results.data.productos;
vm.totalUsers = results.data.count;
},
function (error) {
alert(error.data.message);
});
};
vm.destroy = destroy;
vm.edit = edit;
vm.getResultsPage = getResultsPage;
});
Analizando una Vista
Ahora es momento de analizar una de las vistas gestionada por los controllers; en este caso productoForm.html – la cual es gestionada por productoController -, en su interior podemos apreciar una serie de directivas que son parte de AngularJS, tal como ng-model, que tiene por finalidad brindar el data binding de doble direccionalidad, una de las características más importantes de este framework, además apreciamos el uso dg-repeat, directiva que nos permite iterar un conjunto de datos, en este caso una lista de unidades que serán mostradas dentro de un combobox. A su vez podemos visualizar el uso de directivas de validación como ng-required que señala que dicho dato es obligatoriamente requerido, y finalmente de directivas que vinculan acciones dentro de la vista con funciones definidas en el controller, tal es el caso de la directiva ng-click la cual vincula dicho evento del botón grabar con la función save() del controller, y la función cancel() con el evento de click del botón cancelar.
<div class="panel panel-primary">
<div class="panel-heading">Nuevo Producto</div>
<div class="panel-body">
<form class="form-horizontal" role="form" id="productoForm" name="productoForm">
<div class="form-group">
<label for="id" class="col-sm-3 control-label">ID</label>
<div class="col-sm-6">
<input type="text" id="id" name="id" class="form-control" readonly="readonly"
ng-model="vm.producto.id" placeholder="id" />
</div>
</div>
<div class="form-group">
<label for="nombre" class="col-sm-3 control-label">Nombre</label>
<div class="col-sm-6">
<input type="text" id="nombre" name="nombre" class="form-control"
ng-model="vm.producto.nombre" ng-required="true" placeholder="nombre" />
</div>
</div>
<div class="form-group">
<label for="precio" class="col-sm-3 control-label">Precio</label>
<div class="col-sm-6">
<input type="text" id="precio" name="precio" class="form-control"
ng-model="vm.producto.precio" ng-required="true" placeholder="precio" />
</div>
</div>
<div class="form-group">
<label for="unidad" class="col-sm-3 control-label">Unidad</label>
<div class="col-md-2">
<select data-ng-model="vm.producto.unidadID" class="dropdown form-control" required>
<option data-ng-repeat="unidad in vm.unidades" value="{{unidad.id}}">{{unidad.descripcion}}</option>
</select>
</div>
</div>
<div class="form-group">
<div class="col-md-3 col-sm-3 col-sm-offset-3 col-md-offset-3">
<button id="cancelButton" name="cancelButton" class="btn btn-danger"
ng-click="vm.cancel()">
<i class="glyphicon glyphicon-thumbs-down"></i> Cancelar
</button>
<button id="submitButton" name="submitButton" class="btn btn-primary"
ng-click="vm.save()">
<i class="glyphicon glyphicon-thumbs-up"></i> Guardar
</button>
</div>
</div>
</form>
</div>
</div>
A continuación se muestra la vista que gestiona el controller denominado productoController:
Además mostramos como es la vista que gestiona las altas y bajas de un Producto a través del controller definido como crudProductoController:
Definiendo el Servicio
Tal como vamos analizando el proyecto basado en AngularJS, este nos brinda la capacidad de limitar las responsabilidades dentro de los diferentes componentes que constituyen el código, así tenemos que los controladores se hacen cargo de la lógica, necesitamos de servicios que se hagan cargo de la invocación a los servicios Web API, en esta caso veremos la funcionalidad de productoService, cuya responsabilidad es establecer la conexión con el Servicio Web API y devolver el resultado de las operaciones celebradas, tal como se aprecia a continuación:
webApp.service("productoService", function ($http, confServer) {
this.getProductos = function () {
return $http.get(confServer.apiUrl + "/api/Producto");
};
this.getUnidades = function () {
return $http.get(confServer.apiUrl + "/api/Unidad");
};
this.getProducto = function (id) {
return $http.get(confServer.apiUrl + "/api/Producto/" + id);
};
this.getProductoDTO = function (id) {
return $http.get(confServer.apiUrl + "/api/Producto/dto/" + id);
};
this.getPaginado = function (page) {
return $http.get(confServer.apiUrl + "api/Producto/paging/" + page);
};
this.post = function (Producto) {
var request = $http({
method: "post",
url: confServer.apiUrl + "api/Producto/",
data: Producto
});
return request;
};
this.put = function (id, Producto) {
var request = $http({
method: "put",
url: confServer.apiUrl + "api/Producto/" + id,
data: Producto
});
return request;
};
this.delete = function (id) {
var request = $http({
method: "delete",
url: confServer.apiUrl + "api/Producto/" + id
});
return request;
};
});
Tal como se pudo apreciar el Servicio hace uso de los métodos publicados en nuestra Web API (Get, Post, Put, Delete) a través de una serie de llamadas mediante la función $http, la cual es parte del core de AngularJS y que tiene por finalidad facilitar la comunicación con servicios remotos.
Notas Adicionales:
- Con respecto a los Controllers y Servicios de gestión de las Unidades, su explicación se ha omitido por tener una naturaleza funcional sumamente similar a los Productos.
- Se ha obviado la explicación de la mayoría de vistas html debido a que estás no contienen código invasivo que requiera mayor explicación que las usuales directivas propias del código html.
Referencias: