Categories
español programacion web development

Crear una aplicación sencilla (otra vez) con AngularJS

Hola a todos, ha pasado mucho tiempo desde el último post.

En el post anterior os mostraba cómo hacer una aplicación sencilla usando Backbone.js para crear una single page application. Después de un año y muchas cosas de por medio, en mi toolkit de tecnologías web para el frontend he cambiado Backbone.js por AngularJS.

¿Y por qué ese cambio?

Todo empezó por un interés en conocer este framework Javascript, que si bien ya llevaba existiendo un tiempo, comenzaba a hacerse más popular según iba subiendo de versión, y ya se podía considerar algo más estable. Después de hacer las típicas aplicaciones “Hello World”, seguí adentrándome más en su funcionamiento, los componentes y extensiones, y desde hace un tiempo puedo decir que es mi framework de referencia para afrontar cualquier desarrollo de aplicación web (aplicación en sentido moderno, tipo single page app, con todas las modernidades posibles).

Una de las cosas que me disgustaban de Backbone.js era la cantidad de código javascript que había que meter para poder tener un esqueleto de aplicación, y a partir de ahí, continúa escribiendo el código de negocio de tu aplicación. Es cierto que hay componentes para Backbone.js que agilizan determinadas partes del entorno, pero no me encontraba del todo cómodo dependiendo de tantos añadidos externos, que además había veces que me presentaban un conflicto de dependencias, o no estaban tan depurados como hubiera deseado.

Así que, después de varias semanas probando el entorno decidí dar el salto y centrarme en AngularJS, y desde mi opinión personal, encuentro este framework mucho más ágil, con una mejor separación entre la capa de presentación y la de negocio.

No está en mis objetivos dar una introducción a AngularJS en esta entrada, para eso ya existen muy buenos recursos en la parte de documentación de su sitio web. Lo que yo quiero mostrar al lector es cómo empezar una aplicación sencilla que le permita avanzar al siguiente escalón por su cuenta.

Entrando en materia

Así que para mostrar las similitudes y diferencias entre AngularJS y Backbone.js, he pensado en reescribir el programa del post anterior con esta nueva tecnología y que sea el lector/programador el que saque sus propias conclusiones.

El objetivo del programa ya es conocido: se trata de un buscador de vídeos de Youtube en el cual un usuario introduce una cadena de búsqueda y le aparecen los vídeos relacionados con ese texto. Esta aplicación utiliza la API de búsqueda de Youtube, recibe los resultados en formato JSON (que ya comenté en el post anterior), y extrae los datos necesarios para mostrar la información al usuario.

Para realizar esta aplicación deberemos descargar AngularJS desde el sitio web, o bien enlazarlo desde la CDN de Google de librerías Javascript. Actualmente se encuentra en la versión 1.2.5 en su rama estable, así que haremos uso de esta versión, y para no tener que bajar ningún archivo, enlazaremos directamente al código de la CDN.

AngularJS se basa en el modelo MV*, por lo que en una aplicación que utilice AngularJS se encontrarán controladores y vistas, además de otros componentes como servicios, factorías, directivas (ligadas a la parte de presentación), recursos, etc.

En este programa hay dos archivos, uno que define el aspecto de la aplicación y es el que contiene el código HTML y otro que define la lógica de la aplicación y que está escrito en Javascript. A continuación os voy a mostrar el código de los dos archivos y lo voy a comentar línea a línea, explicando cada parte.

El código para la parte de HTML es el siguiente:

<!DOCTYPE html>
<html ng-app="videos">
	<head>
		<meta http-equiv="content-type" content="text/html;charset=utf-8"/>
		<title>Aplicación de ejemplo en AngularJS</title>
		<style type="text/css">
			* {
				font-family: sans-serif;
			}
			.video {
				width: 350px;
				display: inline-block;
				margin: 15px;
				vertical-align: top;
			}
		</style>
	</head>
	<body ng-controller="AppCtrl">
		<input type="text" ng-model="query">
		<button type="button" ng-click="buscar()">Buscar</button>
		<h1>Vídeos de Youtube con AngularJS</h1>
		<div ng-repeat="video in videos" class="video">
			<h2>'{{video.title}}' por {{video.uploader}}</h2>
			<div>
				<iframe width="320" height="240" ng-src="{{videoEmbedURL(video.id)}}" frameborder="0" allowfullscreen></iframe>
			</div>
			<h3>Descripción</h3>
			<div>{{video.description}}</div>
		</div>
		<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.5/angular.min.js"></script>
		<script type="text/javascript" src="application.js"></script>
	</body>
</html>

Las primeras líneas (1-17) no tienen nada de especial, lo único destacable es el atributo ng-app=”videos” que hay en el elemento html. Este atributo se utiliza para indicarle a AngularJS que el contenido de esta página es una aplicación que debe de gestionar, teniendo el control de todos los elementos extra que define el framework. Por lo demás, está la definición de la cabecera, los estilos y el título.

En la linea 18 se encuentra el elemento body, que igualmente tiene otro atributo de AngularJS, en concreto ng-controller=”AppCtrl”. Este atributo le indica a AngularJS que el contenido de ese elemento estará gestionado por el controlador AppCtrl. Este controlador define una serie de funciones y tiene una serie de propiedades que haran que la aplicación de ejemplo funcione correctamente. Más adelante mostraré el contenido del archivo Javascript que es donde está definido, pero por ahora sigamos examinando el HTML.

En las líneas 19-20 tenemos el elemento input que tiene un atributo nuevo llamado ng-model, el cual vincula el contenido de este campo de entrada con una variable del controlador, llamada query, que veremos más adelante, y después el elemento button, que también tiene una propiedad nueva, ng-click, la cual ejecuta dicha función del controlador cuando el botón es pulsado. El código de de esta función lo veremos después, cuando muestre el contenido del archivo Javascript.

Entre las líneas 22-29 tenemos un elemento div con una particularidad, el atributo ng-repeat, el cual se utiliza para repetir el contenido según la iteración descrita: video in videos. Esto instanciará una variable video que iterará sobre cada elemento del array videos definido en el controlador, exponiendo este objeto y sus propiedades al contenido de este div. Por lo tanto, cuando en las líneas posteriores se hace referencia a {{video.title}}{{video.uploader}}, o {{video.description}}, lo que se está haciendo es mostrar el valor de esas propiedades dentro del texto HTML, como reconocerá cualquiera que haya usado una librería de plantillas HTML como handlebars o moustache.

Lo que requiere mayor explicación es la línea 25. El atributo ng-src del elemento iframe se utiliza para proporcionar un atributo src con valores interpolados desde variables de AngularJS. Como el identificador del vídeo lo tenemos en el objeto video, no podemos hacer referencia a esta propiedad directamente en el atributo src, y por eso es por lo que necesitamos el otro atributo. Además se utiliza la función videoEmbedURL() para generar la URL que porporciona el fuente para este iframe a partir del identificador del vídeo, que veremos más adelante en el archivo Javascript.

Y por último quedan los elementos de las líneas 30-31 que cargan, por un lado, el fuente de AngularJS desde la CDN de Google de librerías Javascript y, por otro lado, el fuente Javascript de la aplicación, que vamos a ver seguidamente.

angular.module('videos', []).
	config(['$sceDelegateProvider', function($sceDelegateProvider) {
		$sceDelegateProvider.resourceUrlWhitelist(['http://www.youtube.com/embed/*']);
	}]).
	controller('AppCtrl', ['$scope', '$http', '$log', function($scope, $http, $log) {
		$scope.query = '';
		$scope.buscar = function() {
			$log.log('Iniciando búsqueda de "' + $scope.query + '"');
			$http.get('http://gdata.youtube.com/feeds/api/videos?v=2&alt=jsonc&max-results=9', {params:{q:$scope.query}}).
				success($scope.fetch_ok).
				error($scope.fetch_failed);
		};
		$scope.fetch_ok = function(result) {
			if (result && result.data && result.data.items) {
				$log.log('Resultados recibidos');
				var videos = [];
				angular.forEach(result.data.items, function(item) {
					videos.push({
						id: item.id,
						title: item.title,
						uploader: item.uploader,
						url: item.content['5'],
						description: item.description
					});
				});
				$scope.videos = videos;
			} else {
				$log.log('Los resultados no son válidos');
			}
		};
		$scope.fetch_failed = function() {
			$log.log('La búsqueda ha fallado');
			alert("¡La búsqueda en Youtube ha fallado!");
		};
		$scope.videoEmbedURL = function(videoId) {
			return 'http://www.youtube.com/embed/' + videoId;
		};
	}]);;

Este es el contenido del archivo Javascript y en él se puede observar cómo está estructurado el código de esta aplicación.

En general, una aplicación web basada en AngularJS se compone de uno o varios módulos, los cuales pueden contener controladores, directivas, factorías, servicios o bloques de configuración. Cada uno de estos componentes tiene su funcionalidad, y no está dentro del objetivo del post explicar cada uno de ellos, sólo me voy a centrar en los que utilizo en esta aplicación.

En la línea 1 se puede observar cómo se define un módulo de AngularJS. En este caso recibe el nombre de videos, y el segundo argumento de la función especifica las dependencias que tiene de otros módulos. Como en este caso es una aplicación muy sencilla, no hay dependencias, pero en otros casos pueden existir varias, ¡o un montón!

Entre las líneas 2-4 se define un bloque de configuración, esto es, dentro de este bloque se configuran componentes de AngualrJS, o propios, de alguna forma concreta que se ajuste a las necesidades de la aplicación. En nuestro caso, y debido a una funcionalidad que incluye AngualrJS 1.2, debemos configurar uno de estos componentes, en concreto el componente de Script Contextual Escaping$sce, el cual se utiliza para limpiar el código HTML que se genera y controlar posibles fallos que introduzcan vulnerabilidades en la aplicación. Lo que se está haciendo en la línea 3 es indicar a AngularJS que las URLs que comienzan por http://www.youtube.com/embed/ las introduzca en una lista blanca de orígenes permitidos, y ya veremos más adelante por qué es necesaria esta configuración.

En las líneas 5-38 se define el código del controlador. Lo primero que se hace es darle un nombre, en este caso AppCtrl, y es este nombre el mismo que hay que indicar en el atributo ng-controller de la parte de HTML, como ya hemos visto antes. Lo que viene a continuación son las dependencias de este controlador, que son objetos definidos dentro de AngularJS con una funcionalidad concreta:

  • $scope es el ámbito que hace de enlace entre el código HTML y el código Javascript. Todo lo que esté definido dentro del objeto $scope estará disponible en el HTML, ya sean propiedades o funciones.
  • $http es el objeto que nos permitirá hacer llamadas a servicios web externos, del mismo modo que la función $.ajax() de jQuery.
  • Y el objeto $log nos permitirá interactuar con la consola del navegador, que como es una aplicación de ejemplo, será utilizada para indicar el transcurso de la aplicación.

Estos objetos son inyectados en el controlador mediante los parámetros de la función y podrán ser utilizados dentro del mismo. Una observación que se puede hacer en la definición es por qué están definidos como cadenas de texto dentro de un array, teniendo la función del controlador como último elemento del array. El controlador se puede definir simplemente como la función que recibe los parámetros directamente, pero esto se hace así para poder sobrevivir a una minificación del código fuente, pues este proceso cambia los nombre de las variables y AngularJS es sensible al nombre de los argumentos de las funciones. Al indicar en los elementos del array el nombre que estos deben tener, los objetos pueden ser inyectados en los parámetros independientemente del nombre que tengan los argumentos.

La línea 6 define la variable que se utiliza en el elemento input y que contendrá la cadena de búsqueda que se va a enviar a Youtube.

Las líneas 7-12 definen la función que realiza la búsqueda. Es invocada cuando el usuario pulsa el botón “Buscar” gracias al atributo ng-click que tiene añadido el elemento button, y muestra por consola la cadena de búsqueda a enviar, y hace una llamada GET a un servicio de Youtube, indicando los callbacks para éxito o error.

Las líneas 13-30 definen el callback para el éxito en la búsqueda de Youtube. El argumento de esta función es el objeto que se obtiene al deserializar el JSON recibido en la respuesta. Este objeto se recorre y se extraen determinados valores que son los que se van a mostrar en la parte de presentación de la aplicación, como el título, la persona que ha subido el vídeo, la descripción o el identificador del vídeo. Los elementos extraídos se añaden a un array y este se asigna a la propiedad $scope.videos, que como recordaréis, es el array que se recorre en la parte de presentación, en el elemento div de la línea 22.

En las líneas 31-34 se define el callback para el caso de error de la consulta al servicio web de Youtube, el cual lo único que hace es mostrar por consola y en una alerta, un mensaje de error.

Y por último en las líneas 35-37 se define la función que generará la URL para el orígen del iframe que contendrá el vídeo. Esta función lo único que hace es devolver la concatenación de la base de la URL de Youtube junto con el identificador del vídeo que le llega como argumento. El motivo de hacer esto así es por las medidas de seguridad introducidas en AngularJS a partir de la versión 1.2, que impiden hacer concatenaciones en los atributos src o ng-src que puedan resultar inseguras.

¿Esto es todo sobre AngularJS?

En absoluto. AngularJS es mucho más extenso, y hay otras formas de hacer lo que se ha expuesto en esta entrada, pero como una introducción es suficiente, no hay necesidad de complicar demasiado la aplicación.

Otros componentes de AngularJS de los que no se ha hablado son las factorías, los servicios, las directivas, pero estos son material para un futuro post, que espero que llegue antes de que vuelva a pasar un año ;-)

Y hasta aquí ha llegado el ejemplo, espero que os haya resultado provechoso, e igual que el anterior en Backbone.js, también está disponible en mi repositorio de GitHub

Categories
web development

Web developers cheat sheet compilation

Click on the post title and you will get to a wonderful compilation of different cheat sheets covering CSS(3), HTML(5) and jQuery