MEAN: AngularJS

Para finalizar con las entradas relacionadas con el stack MEAN, me dispongo a describir las características de AngularJS. Antes de comenzar me gustaría señalar que toda la información recogida en este artículo se refiere a la versión 1.x de AngularJS, nada que ver con Angular 2 y sus versiones posteriores. Sin embargo, debido a la gran cantidad de aplicaciones desarrolladas con la versión 1.x, no parece que vaya a desaparecer en un futuro cercano, ya que su desarrollo continua activo.

Comenzaré explicando lo que es un SPA (Single Page Application) y para qué sirve, para seguidamente hablar por encima de MVC (Modelo-Vista-Controlador) y finalmente detallar los pormenores de AngularJS con algunos ejemplos e imágenes.

Tabla de Contenidos

Single Page Applications (SPA)

Uno de los principales objetivos en el desarrollo de aplicaciones web es conseguir la mejor experiencia de usuario posible. Esto se logra, principalmente, disminuyendo los tiempos de espera entre vistas, intentando conseguir la mayor semejanza con las aplicaciones nativas.

En las aplicaciones web clásicas la lógica de negocio se realiza en su mayor parte en el servidor, siendo éste el encargado de comunicarse con la lógica de datos y decidir qué mostrar y qué no dependiendo en gran medida de la URL que se quiere consultar, esto es, de la vista que se quiere mostrar. Para el navegador cada URL es independiente, por lo que, aunque en cada vista se carguen los mismos ficheros de estilos y/o plantillas, éstos tienen que volver a ser procesados. Esto crea un problema de latencia que perjudica la experiencia de usuario.

Otro problema que presentan las aplicaciones web clásicas es que su mantenimiento resulta muy costoso debido a la dificultad existente a la hora de transmitir información entre una vista y otra, que implica utilizar variables de sesión cuya especificación ambas vistas tienen que compartir o cualquier otra solución que el desarrollador crea mejor.

Un SPA (Single-Page Application) o aplicación de página única es una aplicación web que cabe en una sola página con el propósito de dar una experiencia más fluida al usuario. De esta forma, una vez el usuario entra por primera vez, toda la lógica de la interfaz de usuario se descarga en el navegador y así puede interaccionar con la misma y navegar por sus contenidos de forma sencilla sin tener que cambiar de página, ahorrándose así los tiempos de carga.

En un SPA las vistas pasan a denominarse estados, contando éstos con vistas o plantillas independientes que se mostrarán dependiendo del estado activo. Los cambios de estado se producen cuando el usuario realiza cualquier interacción con la aplicación web, modificando las zonas pertinentes de la aplicación web para mostrar las vistas y elementos del nuevo estado.

En una aplicación web de este tipo podemos encontrar elementos comunes y áreas de intercambio de vistas. Los elementos comunes son aquellos que permanecerán estáticos en la mayoría de los estados, mientras que en las áreas de intercambio de vistas se producirán los cambios de estado, mostrando la información necesaria que se corresponda con cada estado diferente.

Si la aplicación requiriera de una comunicación con el servidor externo, ya sea para comunicarse con la base de datos, o para realizar alguna tarea que requiera de una mayor velocidad de proceso, lo haría de forma asíncrona utilizando AJAX que permite realizar peticiones HTTP a una API REST que se encargue de realizar las tareas pertinentes en el servidor y devuelva al cliente información relevante para que el navegador informe al usuario de la situación de cada petición.

En la Figura 1 podemos comparar los ciclos de vida de una aplicación web clásica y un SPA. Mientras que las tradicionales requieren de recargas para cada vista diferente siendo el servidor el encargado de suministrar el código HTML para cada petición distinta, en un SPA se realiza una petición inicial al servidor por la que el servidor devuelve un conjunto de recursos que el navegador utilizará para mostrar al usuario la aplicación web. Mientras que la aplicación no requiera de información adicional del servidor el navegador no necesitará realizar ningún tipo de petición adicional, pero en el caso de que así fuera, la petición se realizaría de forma asíncrona y el servidor no devolvería una vista completa, si no que solo devolvería información estructurada relacionada con la petición realizada, como, por ejemplo, un listado de productos.

Figura 1: Ciclos de Vida de una Web tradicional y un SPA

Existen, sin embargo, algunas desventajas en el uso de SPA que requieren de una atención y trabajo especial. Entre ellas, la más importante debido a la propia definición de un SPA, es la dificultad para optimizar su rastreo por motores de búsqueda, ya que los rastreadores no ejecutan código Javascript, que es necesario en los cambios de estado de un SPA. Por este motivo no es recomendable utilizar SPAs en un entorno en el que el SEO sea un requisito, ya que la optimización para buscadores en este caso no es trivial, al menos por ahora.

Otra desventaja es el obstáculo que se encuentra el usuario al hacer uso de los botones de navegación del navegador, esto es, ir a la página anterior o a la siguiente. Al tratarse de una página única, al intentar realizar estas acciones las páginas anteriores o siguientes se corresponden con las páginas que se accedieron antes o después de ingresar en la SPA. Para esto existen soluciones bastante sencillas haciendo uso de los métodos pushState y replaceState de la especificación de HTML5

Teóricamente, se puede diseñar un SPA utilizando tan solo la especificación de HTML 4, sin embargo, en HTML 5 encontramos varias APIs que facilitan en gran medida el desarrollo de las mismas. Por otro lado, existen muchas herramientas que nos permiten crear SPAs de forma efectiva, entre ellas se encuentran diferentes frameworks como Ember.js, Backbone.js, ReactAngularJS y Angular. Todos ellos cumplen la arquitectura MVC o derivados.

MVC

El modelo–vista–controlador (MVC) es un patrón de arquitectura de software que separa los datos y la lógica de negocio de una aplicación de la interfaz de usuario y el módulo encargado de gestionar los eventos y las comunicaciones. Para ello MVC propone la construcción de tres componentes distintos que son el modelo, la vista y el controlador, es decir, por un lado define componentes para la representación de la información, y por otro lado para la interacción del usuario. Este patrón de arquitectura de software se basa en las ideas de reutilización de código y la separación de conceptos, características que buscan facilitar la tarea de desarrollo de aplicaciones y su posterior mantenimiento. (Wikipedia)

Modelo

Se encarga de proveer datos. Suele ser una API. También puede ser un objeto JavaScript o cualquier otra cosa. Es la representación de la información que va a manejar el sistema y se encarga por tanto de controlar los accesos a la misma y la forma en la que se devuelven. Envía a la ‘vista’ aquella parte de la información que en cada momento se le solicita para que sea mostrada. Las peticiones de acceso o manipulación de información llegan al ‘modelo’ a través del ‘controlador’.

Vista

Representa cómo se muestra el modelo al usuario. Es dónde el usuario puede interaccionar con la aplicación.

Controlador

Se sitúa en el centro del sistema y se encarga de decidir cómo procesar los datos del usuario, de dónde obtener los datos, qué hacer con ellos, etc. Invoca peticiones al ‘modelo’ cuando se hace alguna solicitud sobre la información (por ejemplo, editar un documento o un registro en una base de datos). También puede enviar comandos a su ‘vista’ asociada si se solicita un cambio en la forma en que se presenta el ‘modelo’ (por ejemplo, desplazamiento o scroll por un documento o por los diferentes registros de una base de datos), por tanto se podría decir que el ‘controlador’ hace de intermediario entre la ‘vista’ y el ‘modelo’.

Conclusión

Utilizar este patrón nos proporciona, entre otras, las siguientes ventajas:

  • Permite cambiar la fuente de datos para una vista sin necesidad de cambiar el código de éstas vistas.
  • Posibilita cambiar el diseño de una vista, por ejemplo, una nueva librería CSS, sin necesidad de cambiar la lógica de interfaz de usuario.

La mayoría de los frameworks MVC que nos permiten crear SPAs deben cumplir las siguientes características:

  • Enlace de datos de doble vía: Si cambiamos algo en la vista, el modelo cambia de forma automática. Del mismo modo, si el modelo cambia, la vista a su vez se actualiza de forma inmediata.
  • Inyección de dependencias: Técnica en la que el propio framework se encarga de cargar los módulos necesarios en las diferentes estructuras del código y en el momento en el que vaya a usarse. Por ejemplo, si en un módulo necesitamos la librería NetworkGraphVisualization, y esta a su vez necesita D3Visualization y SliderWidget, las cuales, además, necesitan jQuery, el framework de inyección de dependencias se encargará de todo este trabajo por nosotros. Esto nos permite cumplir el patrón de separación de conceptos.
  • Enrutamiento: Capacidad de indexar una vista en la aplicación con una URL específica y poder cambiar la URL cuando la aplicación cambie de estado.
  • Plantillas: Permite definir una estructura HTML en la que se introducen espacios con marcas que se podrán sustituir con la información necesaria para cada situación por medio de simples reemplazos de texto. Las plantillas pueden ser sencillas cadenas de texto con códigos de reemplazo o ficheros HTML preparados.

AngularJS

AngularJS es un framework de JavaScript de código abierto mantenido por Google. Su objetivo es aumentar las aplicaciones basadas en navegador con capacidad de MVC, en un esfuerzo para hacer que el desarrollo y las pruebas sean más fáciles.

La comunidad de desarrolladores generando consultas y soluciones en la red es muy importante, habiendo creado un ecosistema lo suficientemente potente como para permitir resolver cualquier complicación en el menor tiempo posible encontrando siempre soluciones óptimas.

Una de las desventajas de AngularJS es que la curva de aprendizaje es un poco abrupta. La mayor parte de los desarrolladores coinciden en que, a pesar de que el entorno les encanta, cuando encuentran nuevas dificultades les es complicado entender los conceptos necesarios para continuar. Sin embargo, una vez entendidos los conceptos y obtenidos los conocimientos necesarios para avanzar, lo recién aprendido hace que el entusiasmo vuelva a crecer. En la Figura 2 podemos encontrar un ejemplo hilarante de los sentimientos con los que se encuentra todo desarrollador de Angular.

Figura 2: Reacciones en el aprendizaje de Angular JS

Está basado en la creación de etiquetas HTML personalizadas así como atributos que son interpretados por el framework y que ayudan a reescribir las partes necesarias de la aplicación. Además de las etiquetas personalizadas crea diferentes conceptos con la idea de estructurar correctamente cada aplicación y de esa manera hacerla más mantenible. Entre los componentes de AngularJS más importantes encontramos los siguientes:

  • Vistas: Conforman la interfaz de usuario y definen como se muestra la información al usuario.
  • Controladores: Trabajan con toda la lógica detrás de la interfaz de usuario y hacen uso de los Servicios para obtener información.
  • Servicios: Se encargan de la comunicación con el servidor y mantiene ordenado el código con funcionalidades relacionadas.
  • Directivas: Facilitan la tarea de crear etiquetas HTML personalizadas extendiendo su funcionalidad creando nuevos elementos, atributos y comportamientos.

La estructura de una aplicación AngularJS es bastante legible, esto es, la mayor parte de sus métodos están nombrados de una forma muy descriptiva, de tal forma que con solo leerlos es fácil intuir qué es lo que hacen.

Módulos

Cómo mínimo, toda aplicación en AngularJS necesita que sea definido un módulo principal. Un módulo es un contenedor de partes de una aplicación. En la creación de éstos se pueden incluir dependencias como otros módulos o librerías, por lo que la modularización resulta bastante simple. Dentro de un módulo se deben definir el resto de componentes, es decir, todo elemento en AngularJS pertenece a un módulo. En el Fragmento de Código 1 podemos ver el código necesario para inicializar un módulo llamado “myApp” en una aplicación. Si quisiéramos incluir alguna dependencia, cómo puede ser otro módulo o librería, lo haríamos introduciéndolos como un array de cadenas en el segundo parámetro. Como podemos ver en el ejemplo por defecto se introduce un array vacío.

var app = angular.module('myApp', []);

Para referenciar al módulo principal en el código HTML utilizaremos el atributo ng-app que introduciremos en la etiqueta dónde queramos inicializar la aplicación. Por lo general este atributo se introduce en la etiqueta html.

Controladores

Los controladores se encargan de manipular la información en las aplicaciones Angular. Son simples objetos Javascript con métodos y propiedades. Se definen siempre dentro de un módulo.

Entre los objetos más importantes que puede manejar un controlador se encuentra la variable $scope. Esta variable es el enlace entre el código HTML (vista) y el código JavaScript (controlador). En ella se pueden introducir métodos y variables que podrán ser utilizados y modificados desde ambas partes de la aplicación, de tal forma que si el controlador cambiara este objeto, la vista se vería directamente afectada y cambiaría de forma inmediata cualquier información relacionada que se estuviera mostrando al usuario en ese momento. En el Fragmento de Código 2 podemos ver un ejemplo de definición de controlador que hace uso de la variable $scope y le asigna un valor a una variable del mismo.

app.controller('myController', function($scope) {
    $scope.name = 'Miguel';
    $scope.languages = ['JavaScript', 'Python', 'PHP'];
});

Para indicar en el código HTML qué etiqueta padre queremos que sea utilizada y enlazada con un controlador concreto haremos uso del atributo ng-controller.

Directivas

Angular JS permite extender el lenguaje HTML con nuevos atributos y etiquetas denominados directivas. Angular provee muchas directivas predefinidas que añaden funcionalidad a las aplicaciones de forma nativa, pero también nos permite crear directivas personalizadas. Todas las directivas predefinidas comienzan con el prefijo “ng“, por lo que no conviene utilizar ese mismo prefijo para crear directivas personalizadas. Entre las directivas predefinidas más utilizadas se encuentran las siguientes:

  • ng-app: Inicializa una aplicación en Angular JS. El valor que se le da hace referencia al nombre del módulo principal.
  • ng-controller: Define el controlador a utilizar dentro de la etiqueta HTML en la que se ha asignado.
  • ng-model: Enlaza controles HTML (input, select, textarea) con los datos de la aplicación. Por lo general los valores que toma este atributo suelen ser los nombres de las variables definidos en la variable $scope en el controlador.
  • ng-repeat: Permite repetir una porción de código HTML en relación a una variable iterable inicializada en un atributo ng-init o en la variable $scope del controlador.

En el Fragmento de Código 3 podemos ver un ejemplo de código HTML en el que se han utilizado todas las directivas anteriormente mencionadas.

<html ng-app="myApp">
    <head>
        <script src="js/angular.min.js"></script>
        <script src="js/app.js"></script>
    </head>
    <body ng-controller="myController">
        <p>Name: <input type="text" ng-model="name"></p> 
        <h1>Hello {{name}}</h1>
		<ul>
			<li ng-repeat="lang in languages">
				{{lang}}
			</li>
		</ul>
  </body>
</html>

Como podemos observar definimos la aplicación en la etiqueta html, sin embargo esto no es necesario, ya que podría inicializarse en cualquier etiqueta. Dentro de la etiqueta head encontramos la referencia a la librería de Angular JS mínima necesaria para que todo funcione, y al fichero app.js de nuestra aplicación que es dónde se ha definido el módulo principal que denominamos “myApp” y el controlador “myController” que vimos en ejemplos anteriores, es decir, app.js contiene únicamente el Fragmento de Código 2 y el Fragmento de Código 3.

Definimos el controlador en la etiqueta body, pero del mismo modo que sucedía con el atributo ng-app, podemos definirlo en cualquier otra etiqueta siempre y cuando la etiqueta del controlador sea la misma, o hija de la etiqueta en la que se define el atributo ng-app.

Las expresiones de Angular se introducen en el código HTML entre llaves dobles. De esta forma, la expresión {{name}} será sustituida por la variable name perteneciente a $scope, o a cualquier expresión que sea capaz de relacionarse. Las expresiones en Angular son muy parecidas a las expresiones de JavaScript, por tanto, podríamos utilizar expresiones del tipo {{ 5 + 5 }} que mostraría el resultado de la suma en el navegador, o concatenaciones como {{ nombre + “ “ + apellidos }} que mostraría en la página la concatenación de las variables nombre y apellidos.

El resultado de visualizar el Fragmento de Código 3 en un navegador HTML se puede observar en la Captura 2.

Captura 2: Ejecución de Aplicación Angular

Todas las expresiones de Angular han sido sustituidas por sus valores. Con el uso de ng-repeat hemos conseguido que se repita una etiqueta li tantas veces como elementos existen en el array asociado. Por otro lado, hemos enlazado el modelo de datos “name” a la etiqueta de tipo input, por lo que si modificáramos el contenido de la caja, éste cambiaría de forma automática como podemos observar en la Captura 3. Del mismo modo, si en alguna parte del código de la aplicación se modificaran las variables name o languages, los valores que se muestran en el navegador cambiarían de forma automática.

Captura 3: Modificación de valores del modelo asociado

Directivas Personalizadas

Otra de las grandes ventajas de Angular es que nos permite crear nuestras propias directivas asociadas a etiquetas HTML personalizadas. Haciendo uso de este tipo de directivas podemos reutilizar código HTML de una forma muy eficiente, además de poder personalizar el comportamiento y las características de cada una de estas directivas de forma sencilla. Esto nos permite estructurar una aplicación web en distintos componentes con su propio código HTML con sus propios controladores y servicios.

La creación de una directiva propia se hace mediante la función directive asociada a los módulos Angular. Es importante tener en cuenta que los nombres de las directivas en el código HTML y en el JavaScript no son exactamente igual. Si la etiqueta HTML asociada a la directiva se llamara “my-directive” el nombre que habrá que darle a la directiva en su creación en el código de la aplicación será haciendo uso del estilo de escritura “lowerCamelCase”, dónde la primera letra de cada palabra se escribe en mayúsculas a expepción de la inicial, siendo así el nombre de la directiva myDirective. En Fragmento de Código 4 podemos ver un ejemplo de creación de directiva en la que hemos incluido parte del código del ejemplo anterior visto en el Fragmento de Código 3.

app.directive('myDirective', function() {
    return {
        template : 
       '<p>Name: <input type="text" ng-model="name"></p>' 
       +'<h1>Hello {{name}}</h1>'
		  +'<ul>'
		  +	'<li ng-repeat="lang in languages">'
		  +		'{{lang}}'
		  +	'</li>'
		  +'</ul>'
    };
});

Con esta simple directiva nuestro código HTML quedaría como en el Fragmento de Código 5, dando como resultado exactamente el mismo que pudimos observar en la Captura 2 y la Captura 3.

<html ng-app="myApp">
    <head>
        <script src="js/angular.min.js"></script>
        <script src="js/app.js"></script>
    </head>
    <body ng-controller="myController">
        <my-directive></my-directive>
    </body>
</html>

Una directiva no tiene porqué utilizarse solo como una etiqueta. Esta característica nos otorga gran versatilidad a la hora de utilizarlas. Se puede invocar una directiva de las siguientes formas, obteniéndose el mismo resultado en todas ellas:

  • Como un elemento o etiqueta HTML:
    • <my-directive></my-directive>
  • Como un atributo:
    • <div my-directive></div>
  • Como una clase:
    • <div class=”my-directive”></div>
  • Como un comentario:
    • <!– directive: my-directive –>

Sin embargo, una directiva puede restringirse para que solo pueda ser utilizada de una de las formas anteriormente especificadas. Para ello se hace uso de la propiedad restrict, que toma como valores alguno de los siguientes, o combinaciones de ellos:

  • E: solo como elemento o etiqueta
  • A: solo como atributo
  • C: solo como clase
  • M: solo como comentario

Como mencionábamos anteriormente una directiva puede contener su propio controlador. Para ello solo habría que definir la función que hará de controlador en el parámetro link. Existe otro parámetro de características similares pero que se ejecutará en otro momento de la carga de la aplicación, que es el parámetro controller. Para saber en qué momento utilizar una y otra hay que tener en cuenta la siguiente diferenciación:

  • link: Se ejecuta después de la compilación
  • controller: Se ejecuta antes de la compilación

Otra propiedad interesante dentro de las directivas es templateUrl, que nos permite especificar una ruta a un fichero html en el que se encontrará el código html de la directiva. De esta forma nos ahorraremos utilizar cadenas de texto con código html en el propio código de la aplicación.

En el Fragmento de Código 6 podemos observar una directiva completa en la que se hace uso de todos los parámetros anteriormente mencionados.

app.directive('myDirective', function() {
    return {
	   restrict: 'E',
        templateUrl : 'templates/mydirective.html',
        controller: function($scope, $element){
			$scope.name = $scope.name + ' Sánchez';
		  },
	    link: function(scope, element, attr) {
	      scope.name = scope.name + ' Mendoza';
	    }
    };
});

Cómo podemos observar el controlador recibe los siguientes parámetros:

  • $scope: Misma variable general utilizada en el controlador principal. De no existir controlador principal $scope estaría vacía, pero podría utilizarse dentro de la directiva de forma independiente.
  • $element: Esta variable contiene todas las propiedades y estados del objeto HTML que invoca a la directiva, de tal forma que podemos acceder desde este elemento a otros atributos introducidos en el mismo como clases o estilos y modificarlos si hiciera falta.

Por otro lado, la declaración del valor del parámetro link recibe los siguientes atributos equivalentes:

  • scope: Se refiere a la misma variable scope que recibe el parámetro controlador.
  • element: Al igual que sucede con scope, element es el mismo objeto que recibe el parámetro $element del controlador.
  • attr: Relación de atributos del objeto que invoca la directiva.

En el fichero templates/mydirective.html podemos encontrar el código HTML que anteriormente utilizábamos como cadena de texto en el Fragmento de Código 3 e inicialmente en el Fragmento de Código 3.

En la Captura 4 podemos observar el resultado de ejecutar la aplicación. En ella se puede apreciar el comportamiento de los parámetros link y controller, ejecutándose controller antes que link.

Captura 4: Resultado de utilización de parámetros link y controller

Es importante señalar que a los parámetros link y controller no solo los diferencia el orden de ejecución. Cuando el parámetro controller es ejecutado la aplicación no ha terminado de cargarse todavía, es por eso que este parámetro no recibe el parámetro de atributos que si recibe link, ya que link se ejecuta cuando la página se ha cargado completamente y se puede acceder a todos los objetos y propiedades de los mismos.

Existen otros parámetros y usos más avanzados para las directivas que se salen el contexto de una introducción. Para más información acerca de cómo utilizar las directivas remitimos al lector a la Guía de Desarrollador de AngularJS.

Servicios

En Angular los servicios son funciones u objetos disponibles en todo el entorno de una aplicación, destinados a realizar tareas concretas y a proporcionar información específica a los controladores o a cualquier otro elemento que haga uso de ellos. Angular contiene más de 30 servicios predefinidos. En muchas ocasiones estos servicios predefinidos proporcionan información que podría obtenerse de otras formas dentro de lo que es la ejecución de código JavaScript, como obtener la url de la página actual, o el título del documento. Sin embargo, y puesto que Angular está constantemente revisando el estado de la aplicación, es recomendable utilizar estos servicios en lugar de los métodos estándar, para de esa manera se mejore la comunicación entre los diferentes módulos y objetos de nuestra aplicación.

Los servicios se utilizan para compartir funcionalidades en una aplicación, de tal forma que no haya que repetir código. Un ejemplo podría ser crear un servicio de acceso a una tabla concreta de una base de datos con todos los métodos de consulta y modificación. Cualquier parte de la aplicación que necesitara consultar esa tabla solo tendría que utilizar el servicio sin necesidad de crear sus propios métodos de acceso a la base de datos o al servidor pertinente.

Un aspecto importante de los servicios es que Angular solo los inicializa cuando van a ser utilizados, y una vez son cargados en memoria, si diferentes objetos hacen uso de un mismo servicio, Angular no crea diferentes versiones del servicio para cada componente, si no que crea una sola instancia que comparten todos los componentes de forma segura.

Para que un controlador pueda hacer uso de un servicio, éste debe de ser pasado como parámetro en la definición del controlador. Entre los servicios predefinidos destacan los siguientes:

$location

Proporciona información acerca de la URL que aparece en la barra de dirección del navegador, haciéndola disponible para toda la aplicación. Además proporciona métodos para modificar la URL actual y para navegar entre los distintos enlaces de una aplicación. Incluye métodos de respuesta ante cambios de URL, de tal manera que podemos procesar cualquier URL antes de que sea mostrada. En general, cambios en la url de la barra de direcciones se reflejan inmediatamente en el servicio $location y viceversa.

En él podemos observar la declaración de un controlador que hace uso del servicio $location para obtener el puerto en el que se está ejecutando la aplicación, y almacenarlo en la variable de sesión puerto.

var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope, $location) {
    $scope.puerto = $location.port();
});

$http

Este servicio es uno de los más utilizados en una aplicación Angular, ya que permite realizar peticiones de tipo http a un servidor y obtener información de respuesta. Esto se utiliza, entre otras cosas, para cargar objetos de base de datos en una variable y poder mostrarla correctamente en la página de la aplicación.

Los métodos que implementa se corresponden con las peticiones http más utilizadas que son GET, POST, PUT y DELETE entre otras, lo que nos permite consumir APIs de tipo REST sin ningún problema y de una forma bastante sencilla.

Incorpora métodos para el tratamiento de la información recibida de forma asíncrona. Esto permite que la aplicación no tenga que quedarse esperando a que el servidor devuelva los datos para poder manejarlos, si no que una vez realizada la petición, la aplicación continúa su ejecución y, en cuanto llega la información solicitada, se ejecuta el código que se haya introducido como método de respuesta.

En el Fragmento de Código 8 podemos ver un ejemplo de cómo cargar la variable languages de la aplicación de forma externa haciendo uso del servicio $http. El resultado será equivalente al obtenido anteriormente en ejemplos anteriores que pudimos observar en la Captura 3.

app.controller('myController', function($scope, $http) {
    $scope.name = 'Miguel';
    $http
        .get('data/languages.json')
    	   .then(function (response) {
        	$scope.languages = response.data.languages;
    	});
});

En este caso la información de la variable languages se obtiene de un objeto de tipo JSON incluido en el fichero languages.json. Dentro del objeto “response” se encuentra toda la información relacionada con la respuesta obtenida en la petición GET. Entre las propiedades de este objeto se encuentra el objeto “data” que incluye toda la información devuelta por la petición. Al tratarse de un objeto JSON, Angular convierte automáticamente el contenido a un objeto JavaScript, en este caso un array, y lo trata como tal. El contenido del fichero languages.json se puede observar en el Fragmento de Código 9.

{
	"languages": ["JavaScript", "Python", "PHP"]
}

Con el uso de este servicio podríamos implementar fácilmente toda la capa de lógica de datos de una aplicación web habitual, dejando al servidor la tarea de obtener los datos y convertirlos a una cadena JSON.

$timeout e $interval

Estos servicios nos permiten realizar tareas concretas pasado un determinado periodo de tiempo, o de forma repetitiva en intervalos. El comportamiento de éstos es similar a las funciones JavaScript “window.setTimeout” y “window.setInterval”. En el Fragmento de Código 10 podemos observar un ejemplo de ambos servicios.

app.controller('myCtrl', function($scope) {
    $timeout(function () {
        $scope.name = "Antonio";
    }, 2000);
    $interval(function () {
        $scope.name = new Date().toLocaleTimeString();
    }, 5000);
});

En este ejemplo la variable “name” pasaría a ser “Antonio” al pasar 2 segundos, y por otro lado cada 5 segundos se actualizaría la misma variable con el valor de la hora actual.

Servicios Personalizados

Al igual que sucedía con las directivas, en Angular también se pueden crear servicios personalizados. En este caso un servicio personalizado se correspondería con un objeto JavaScript con variables y funciones propias que se podrán utilizar de forma externa desde un controlador que reciba el servicio como parámetro.

Los servicios personalizados deben pertenecer a un módulo de la aplicación. La forma de crear un servicio es similar a las anteriormente vistas. En este caso utilizaremos el método service para definir el servicio. Este método recibe como primer parámetro el nombre del servicio por el que se le conocerá en toda la aplicación, y como segundo parámetro una función u objeto JavaScript que definirá el servicio. En el Fragmento de Código 11 podemos observar un ejemplo de servicio que realiza la tarea de obtener los valores de la variable languages utilizada anteriormente, así como un ejemplo de uso de este servicio en el controlador principal que venimos utilizando, obteniéndose como siempre el mismo resultado.

app.service('Languages', function() {
	this.getLanguages = function() {
	    return ['JavaScript', 'Python', 'PHP'];
	} 
});

app.controller('myController', function($scope, Languages) {
	$scope.name = 'Miguel';
    $scope.languages = Languages.getLanguages();
});

Un servicio puede hacer uso de otros servicios sin necesidad de que éstos últimos sean declarados en el controlador. De esta forma se consigue independizar totalmente las diferentes capas de la aplicación. Esto nos permitiría, cómo podemos ver en el Fragmento de Código 12, obtener los datos de forma remota con el servicio $http desde el servicio Languages personalizado.

app.service('Languages', function($http) {
	this.getLanguages = function() {
		$http
    	  .get('data/languages.json')
    	  .then(function (response) {
        	return response.data.languages;
    	});
	}
});

Sin embargo, hay que tener en cuenta que mientras que la petición en el controlador se hace de forma síncrona, la obtención de datos en el servicio con $http se hace de forma asíncrona. De esta forma, aunque el código anterior no provoque ninguna excepción, el resultado será que al realizar la petición al servicio, éste devolverá una variable no definida, y cuando se haya realizado la petición y obtenido los datos, ese “return” no será recibido por nada.

Para solucionar este problema, Angular provee otro servicio que implementa un sistema de promesas, que vienen a ser métodos para ejecutar funciones de forma asíncrona y poder utilizar los resultados de forma correcta cuando estén listos. Este servicio se denomina simplemente $q, y como podemos ver en el ejemplo del Fragmento de Código 13 el concepto es bastante sencillo.

app.controller('myController', function($scope, Languages) {
    $scope.name = 'Miguel';
    Languages.getLanguages().then(function(response) {
	    $scope.languages = response;
    }); 
});

app.service('Languages', function($http, $q) {
	this.getLanguages = function() {
	   var defered = $q.defer();
        var promise = defered.promise;
        $http
	    	.get('data/languages.json')
	    	.then(function (response) {
	            defered.resolve(response.data.languages);
	    	});
	   return promise;
	}
});

Básicamente, el servicio $q nos devuelve un objeto de tipo defer que será el que utilizaremos para devolver los datos. En ese mismo objeto encontramos una variable llamada promise que será lo que se devolverá de forma inmediata al controlador, y será además la encargada de esperar de forma asíncrona a que se realice la petición, y a obtener los datos en el controlador que devuelva el objeto defered del servicio mediante el método then.

Si en el controlador utilizáramos además el servicio $timeout, como podemos ver en el Fragmento de Código 14, podremos actualizar la variable languages de forma automática en caso de que el contenido del fichero JSON cambie.

app.controller('myController', function($scope, $timeout, Languages) {
    $scope.name = 'Miguel';
    getLanguages = function() { 
	   Languages.getLanguages().then(function(response) {
	       $scope.languages = response;
	       $timeout(getLanguages, 5000);
        }); 
    }
    getLanguages();    
});

Como podemos observar se crea una función llamada getLanguages que llama al servicio utilizando el sistema de promesas que vimos anteriormente. Una vez obtiene los datos, utiliza $timeout para llamarse a si misma transcurridos 5 segundos.

También te podría gustar...

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *