Você está na página 1de 46

AngularJS

Tabla de contenido
1. Introduction
2. Tutorial
3. 0 - Iniciando la aplicacion
4. 1 - Template Estatico
5. 2 - Template Angular
6. 3 - Filtrando Repetidores
7. 4 - Enlace de Datos Bidireccional
8. 5 - XHRs e Inyeccion de Dependencias
9. 6 - Modelando Imagenes y Enlaces
10. 7 - Ruteo y Vistas Multiples
11. 8 - Ms y ms Templates
12. 9 - Filtros
13. 10 - Controladores de Eventos
14. 11 - REST y Servicios Personalizados
15. 12 - Animaciones

AngularJS

Qu es AngularJS?
AngularJS es un framework JavaScript de desarrollo de aplicaciones web en el lado cliente, desarrollado por Google y
utiliza el patrn MVC (Modelo-Vista-Controlador)
A travs de los siguientes capitulos aprenders a construir una aplicacin web con AngularJS.

Introduction

AngularJS

Tutorial
Una muy buena forma de aprender Angular JS es trabajar a travs de este tutorial, en el cual aprenderemos a construir
una aplicacin web. La app que construirs es un catlogo que muestra una lista de dispositivos del Android, te dejer
filtrar una lista para ver los telfonos que te interesen, y luego podrs ver en detalle de cada dispositivo:

Sigue este tutorial para ver como angular hace a tu navegador ms inteligente sin necesidad de utilizar extensiones
nativas o plugins.
Observa ejemplos sobre cmo utilizar data binding (enlace de datos) del lado del cliente para crear vistas dinmicas de
datos que cambian de acuerdo a las acciones de tus usuarios. -Angular guarda las vistas en sincronizacin con tus datos
sin necesidad de manipulacin DOM. Aprende una mejor manera y ms fcil de probar tus aplicaciones web, con Karma y
Protractor. Aprende a utilizar inyeccin de dependencias y servicios para hacer una las tareas ms comunes de la web,
como es obtener datos de tu aplicacin, mucho ms fcil.
Cuando termines este tutorial sers capaz de: Crear una aplicacin dinmica que funciona en todos los navegadores web
modernos. Utilizar enlace de datos (data binding) para conectar el modelo de datos para tus vistas. Crear y ejecutar
prueba unitarias con Karma. Crear y ejecutar pruebas E2E (end-to-end), con Protractor.
Mover la lgica de la aplicacin del template y colocarla en los controladores. Obtener datos de un servidor mediante los
servicios de Angular. Aplicar animaciones a tu aplicacin, mediante ngAnimate. Conocer ms recursos para aprender ms
sobre AngularJS.
Este tutorial te guiar por todo el proceso de creacin de una single page app y aprenders a escribir y ejecutar pruebas
unitarias end-to-end. Pruebas al final de cada paso y sugerencias para que aprendas ms acerca de AngularJS y sobre la
aplicacin que vas a crear.
Puedes ir a travs de todo el tutorial en un par de horas o puedes profundizar ms en cada paso. Si ests buscando una
introduccin ms corta para AngularJS, revisa el documento Getting Started

Tutorial

AngularJS

Primeros Pasos
El resto de esta pgina explica cmo puedes configurar el entorno de desarrollo . Si quieres puedes ir directamente a leer
el resto del tutorial, Encendiendo motores.

Trabajando con el cdigo


Este tutorial se basa en el uso del sistema de control de versiones de software Git. No necesitas tener mucho sobre Git
para seguir el tutorial, solo instalarlo y ejecutar unos pocos comandos.

Instalando Git
Descarga e instala git en tu computadora http://git-scm.com/download una vez instalado, tendrs acceso a la linea de
comandos git. Estos son los principales comandos git que debes conocer:
git clone : clona un repositorio remoto en tu computadora.
git checkout : Recupera un archivo desde la rama o revisin actual.

Descarga angular-phonecat
Clona el repositorio angular-phonecat localizado en GitHub corriendo el siguiente comando:
git clone --depth=14 https://github.com/angular/angular-phonecat.git

Este comando crea el directorio phonecat angular en el directorio actual.


Cambia el directorio actual a: angular-phonecat.
cd angular-phonecat Todas las instrucciones de este tutorial, desde ahora,correran desde el directorio angular-phonecat .
Instala NodeJS

Instala Node desde aqui http://nodejs.org/download/


Revisa la versin de Node que tienes instalado con el siguiente comando: node --version Las distribuciones, basadas en el
sistema operativo Debian tienen un conflicto con la utilidad llamada node Les sugerimos instalar el paquete nodejs-legacy
y renombrar node como nodejs apt-get install nodejs-legacy npm nodejs --version npm --version Una vez que hayas
Node.js instalado en tu mquina, puedes descargar el manejo de dependencias o mdulos corriendo el comando: npm
install El archivo package.json lleva la configuracin del paquete, donde se guardarn las dependencias de paquetes del
proyecto y la configuracin bsica de este.
Descarga las siguientes herramientas en el directorio node_modules Bower - Http-Server - Karma - Protractor - Corriendo
npm install usar automticamente bower para descargar el framework de Angular dentro del directorio
app/bower_components
El proyecto est preconfigurado con un algunos scripts npm que te harn ms fcil realizar las tareas que necesitas hacer
mientras desarrollas la app:
npm start : inicia la aplicacin npm test : Inicia Karma y corre las pruebas unitarias. npm run protractor : Corre las pruebas
Protractor end to end (E2E) npm run update-webdriver : instala los drivers necesarios para Protractor.

Instala las herramientas de Ayuda (opcional)


Tutorial

AngularJS

Los mdulos Bower, Http-Server, Karma y Protractor son tambin ejecutables, los cuales pueden ser instalados
globalmente y ejecutados directamente desde la terminal de comandos.
No es necesario hacer esto para seguir con este tutorial, pero si decides hacerlo, puedes instalar estos mdulos
globalmente utilizando: sudo npm install -g
Por ejemplo, para instalar la lnea de comandos de Bower ejectuta: sudo npm install -g bower omite la palabra sudo si
ests en windows.
A continuacin, puedes ejecutar la utlidad bower directamente, mediante el comando: bower install

Corriendo servidores Web


Aunque Las aplicaciones web de angular corran directamente del lado del cliente y podamos ejecutarlas directamente en
el navegador, es mejor correrla mediante un servidor http.
En particular, por razones de seguridad, la mayora de los exploradores modernos no permitien que JavaScript realice
peticiones al servidor si la pgina se carga directamente desde el sistema de archivos.
El proyecto Angular PhoneCat est configurado para alojar la aplicacin en un servidor web esttico durante la etapa de
desarrollo, inicia el servidor ejecutando: npm start
Esto crear un servidor Web en tu mquina local. que correr en el puerto 8000. Ahora puedes ver tu aplicacin en:
http://localhost:8000/app/index.html

Corriendo pruebas unitarias


Utilizamos pruebas unitarias para asegurarnos que el cdigo JavaScript de nuestra aplicacin funcione correctamente.
El proyecto Phonecat de Angular est configurado para utilizar el Karma para ejecutar pruebas unitarias . Comienza por
poner en marcha Karma: npm test
Con angular podemos escribir tests unitarios que nos permitan definir y comprobar el funcionamiento de los componentes
de la aplicacin por separado.
Con esto correr la prueba unitaria mediante de Karma, un lanzador de tests.
Lee la configuracin del archivo en test/karma.conf.js Esta configuracin dice: Abre tu navegador Chrome y conctalo al
Karma Ejecuta todas las pruebas unitarias en tu navegador Roporta los resultados de estas pruebas en la ventana de la
lnea de comandos del terminal. Ve todos los archivos javascript y vuelve a ejecutar las pruebas, siempre que haya
cambios.

Tests end-to-end
Utilizamos test de extremo a extremo para asegurarnos que nuestra aplicacin funcione correctamente como esperamos.
estn diseadas para probar la aplicacin del lado cliente enteramente, simulan la accin de un usuario y se ejecuta en el
navegador.
Las pruebas de extremo a extremo se guardan en el directorio test/e2e. El proyecto Angular phonecat est configurado
para utilizar Protractor para ejecutar las pruebas de extremo a extremo. Protractor se basa en un conjunto de
controladores que le permiten interactuar con el navegador. Puedes instalar estos controladores ejecutando: npm run
Tutorial

AngularJS

update-webdriver (solo necesitas hacerlo una vez)

Protractor trabaja interactuando con la aplicacin en ejecucin, tenemos que iniciar nuestro servidor web: npm start
Luego ejecuta en una nueva ventana esta linea de comandos: npm run protractor
Protractor leer la configuracin del archivo en el test/protractor-conf.js. Esta configuracin dice lo siguiente: Abre el
navegador chrome y corre tu aplicacin Ejecuta las pruebas de extremo a extremo en el navegador. Reporta los resultados
de estas pruebas en la ventana de la lnea de comandos del terminal. Cierra el navegador.
Es bueno ejecutar las pruebas end-to-end cada vez que hagas cambios a la vistas HTML o desees comprobar que la
aplicacin como un todo, se ejecuta correctamente.

Tutorial

AngularJS

Iniciando la aplicacin
En este paso, te familiarizaras con los archivos ms importantes del cdigo fuente, aprenders cmo comunicarte con
servidores de desarrollo como angular-seed que es bsicamente un esqueleto de una tpica aplicacin AngularJs, y
ejecutars la aplicacin en el navegador.
En el directorio del proyecto angular phonecat, corre este comando:
git checkout -f step-0 Esto nos deja un espacio de trabajo base en el paso 0.
Tienes que repetir este paso a medida que avances en el tutorial, e ir cambiando el numero por el paso en el que estes (ej.
step-0 si ests en el paso 0, step-1 si ests en el paso 1). Esto va a ser que cada cambio en tu directorio se pierda.
Si no lo haz hecho an es necesario instalar las dependencias ejecutando:
NPM install
Para ver como la aplicacin se ejecuta en un navegador, dirigete a tu navegador y a la direccin:
(http://localhost:8000/app/index.html) deberas ver la aplicacin por defecto funcionando.
Ahora puedes ver la pgina en tu navegador. An no es muy emocionante, pero est bien.
La pgina HTML slo muestra el "Aqu no hay nada aqu todava!" pero el cdigo contiene algunos de los elementos ms
importantes, que vamos a necesitar a medida que avancemos en el desarrollo de la aplicacin:
app / index.html:

<! DOCTYPE html>


<html lang = "en" ng-app>
<head>
<charset meta = "UTF-8">
<title> Mi archivo HTML </ title>
<link rel = "stylesheet" href = "bower_components / bootstrap / dist / css / bootstrap.css">
<link rel = href "stylesheet" = "css / app.css">
<script src = "/ bower_components angulares / angular.js"> </ script>
</ head>
<body>
<p> No hay nada aqu {{'todava' + '!'}} </ p>
</ body>
</ html>

Qu est haciendo el cdigo?


La directiva ng-app:
<html ng-app>

0 - Iniciando la aplicacion

AngularJS

Ng-app es la directiva encargada de iniciar una aplicacin Angular, indica el elemento raz y se debe colocar como atributo
en la etiqueta que quieres que sea la raz de la aplicacin.
La directiva ng-app define nuestra aplicacin.AngularJS se ejecutar en el mbito que le indiquemos, es decir abarcar
todo el entorno donde usemos el atributo ng-app. Si lo usamos en la declaracin de HTML entonces se extender por todo
el documento, en caso de ser usado en alguna etiqueta como por ejemplo en el body su alcance se ver reducido al cierre
de la misma.
El tag del script de Angular
<script src="bower_components/angular/angular.js">

Este cdigo descarga el script angular.js que registra una devolucin de llamada que se ejecutar por el navegador
cuando la pgina HTML que contiene este completamente descargada.
Cuando la devolucin de llamada se ejecuta, Angular busca la Directiva ngApp. Si la Directiva encuentra Angular, se inicia
la aplicacin con la raz de la aplicacin DOM siendo el elemento sobre el que se defini la Directiva ngApp.
Expresin Doble llave {{}} Con estas expresiones tambin enriquecemos el HTML, ya que nos permiten colocar
cualquier variable dentro de ella y AngularJS se encargar de evaluarla, interpretarla y resolverla.
Nada por aqu {{'an' + '!'}} Esta lnea muestra las caractersticas fundamentales de las capacidades de angular:

Binding, es enlace de datos entre la vista y el controlador expresada por dobles llaves
La expresiones en AngularJS son escritas dentro de dobles llaves: {{ expresin}} Las expresiones en Angulars enlazan
datos al HTML de la misma forma que la directiva ng-bind. AngularJS mostrar los datos de salida exactamente donde la
expresin fue escrita. Las expresiones de AngularJS son mucho ms que las expresiones de JavaScript: Estas pueden
contener literales, operadores y variables. Por ejemplo:

Iniciando Aplicaciones Angular


Puedes iniciar aplicaciones Angular de forma automtica utilizando la directiva ngApp es muy fcil y es conveniente para la
mayora de los casos. En casos avanzados, como cuando se utilizan scripts, puedes utilizarlo de manera manual e
imperativa. Cuando inicias la aplicacin suceden 3 cosas importantes:
Se crea el inyector que se utilizar para la inyeccin de dependencia.
El inyector crear el root scope, que se convertir en el contexto para el modelo de nuestra aplicacin.
Angular compilr el DOM empezando por el elemento raz ngApp, procesar las directivas y los bindings que se
encuentran en el documento.
Una vez que iniciaste la aplicacin, se esperarn eventos en el navegador (como un clic, la pulsacin de una tecla o una
respuesta HTTP) que podran cambiar el modelo.
Cuando ocurre un evento, Angular detecta si este caus cambios en los modelos y si se encuentran cambios, Angular lo
refleja en la vista actualizando todos los enlaces afectados.
La estructura de nuestra aplicacin actualmente es muy sencilla. La plantilla contiene una directiva, un enlace esttico, y
nuestro modelo an est vaco, pero pronto va a cambiar!

0 - Iniciando la aplicacion

AngularJS

Qu tipos de archivos hay en nuestro directorio de trabajo?


La mayora de los archivos en el directorio de trabajo viene del proyecto angular-seed es una plantilla que se utiliza para
comenzar a crear aplicaciones web AngularJS.
La plantilla est pre-configurada para instalar el framework angular (va bower en los componentes de la aplicacin/bower /
carpeta) y las herramientas para el desarrollo de una aplicacin web tpica (via npm)
A los efectos de este tutorial, modificaremos la plantilla angular-seed con los siguientes cambios:
Remover la aplicacin de ejemplo Agregar imagenes de telefonos a app/img/phones/ Agregar informacin de los
telefonos (JSON) to app/phones/ *Agregar la dependencia en Bootstrap en el archivo bower.json

Experimentos
Agrega esta nueva expresin al archivo index.html que resuelva esta operacin matemtica:
<p>1 + 2 = {{ 1 + 2 }}</p>

0 - Iniciando la aplicacion

10

AngularJS

Template Estatico
Para ver cmo Angular mejora el estndar HTML, puedes crear una pgina HTML puramente esttica y luego examinar
cmo podemos convertir este cdigo HTML en una plantilla que Angular use para mostrar dinmicamente el mismo
resultado con cualquier conjunto de datos.
En este paso agregaremos informacin bsica sobre dos telfonos celulares a una pgina HTML. Ahora, la pgina
contiene una lista con informacin acerca de los dos telfonos.
Para ver como se modifica el documento html, debemos detener el servidor y ejecutar en la consola el comando: git
checkout -f step-1

Los cambios ms importantes se enumeran a continuacin. Y los puedes ver completos en GitHub app/index.html:

<ul>
<li>
<span>Nexus S</span>
<p>
Fast just got faster with Nexus S.
</p>
</li>
<li>
<span>Motorola XOOM with Wi-Fi</span>
<p>
The Next, Next Generation tablet.
</p>
</li>
</ul>

Experimentos
Intenta aadir ms contenido estatico HTML al documento index.html. como por ejemplo: <p>Total number of phones:
2</p>

Resumen
Agregamos a la aplicacin una lista de contenido estatico, en el segundo paso de este tutorial, aprenderemos a usar
Angular para crear dinamicamente la misma lista de datos.

1 - Template Estatico

11

AngularJS

Template de Angular
Este es el momento de hacer la pgina web dinmica con AngularJS.
Aadiremos una prueba que verifica el cdigo para el controlador que vamos a aadir.Hay muchas formas de estructurar
el cdigo de una aplicacin web.
Para aplicaciones Angular, recomendamos el uso del patrn de diseo Modelo-Vista-Controlador (MVC) para desacoplar el
cdigo y evitar problemas futuros.
La idea de la estructura MVC no es otra que presentar una organizacin en el cdigo, donde el manejo de los datos
(Modelo) estar separado de la lgica (Controlador) de la aplicacin, y a su vez la informacin presentada al usuario
(Vistas) se encontrar totalmente independiente.
Es un proceso bastante sencillo donde el usuario interacta con las vistas de la aplicacin, stas se comunican con los
controladores notificando las acciones del usuario, los controladores realizan peticiones a los modelos y estos gestionan la
solicitud segn la informacin brindada.
Esta estructura provee una organizacin esencial a la hora de desarrollar aplicaciones de gran escala, de lo contrario sera
muy difcil mantenerlas o extenderlas.
Con esto en mente, vamos a usar un poco de Angular y JavaScript para agregar los componentes modelo vista
controlador a nuestra aplicacin.
Ejecutemos este comando en la terminal: git checkout -f step-2
Actualiza tu navegador o consulta en lnea este paso: Step 2 Live Demo. Los cambios ms importantes se enumeran a
continuacin. Los puedes ver completos en GitHub.
En Angular, la vista es una proyeccin del modelo a travs del documento HTML. Cuando el modelo cambia la vista refleja
los cambios y viceversa.
La vista se construye mediante Angular atravs de esta plantilla: app/index.html:

<html ng-app="phonecatApp">
<head>
<script src="bower_components/angular/angular.js"></script>
<script src="js/controllers.js"></script>
</head>
<body ng-controller="PhoneListCtrl">
<ul>
<li ng-repeat="phone in phones">
<span>{{phone.name}}</span>
<p>{{phone.snippet}}</p>
</li>
</ul>
</body>
</html>

Ahora sustituiremos la lista de telfonos mediante la Directiva ngRepeat y dos expresiones de angular:
ng-repeat="phone in phones" attribute in the
Esta directiva te sirve para implementar una repeticin (un bucle). Es usada para repetir un grupo de etiquetas una
2 - Template Angular

12

AngularJS

serie de veces.. Esta repeticin le dice a Angular que cree un elemento <li> por cada telefono.
Las expresiones cerradas entre llaves ( y) sern reemplazadas por el valor de cada expresin.
Hemos agregado una nueva Directiva, llamada ng-controller ng-controller conecta el controlador con el objeto
PhoneListCtrl en la etiqueta body. Las expresiones entre llaves ( { {phone.name}} y { {phone.snippet}} indica enlaces, que
se refieren a nuestro modelo de la aplicacin, que est configurado en nuestro controlador. PhoneListCtr

Modelo y Controlador
El modelo (el array de phones) est ahora instanciado dentro del controlador PhoneListCtrl. El controlador es simplemente
un constructor que toma $scope como parametro. App/JS/Controllers.js:

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


phonecatApp.controller('PhoneListCtrl', function ($scope) {
$scope.phones = [
{'name': 'Nexus S',
'snippet': 'Fast just got faster with Nexus S.'},
{'name': 'Motorola XOOM with Wi-Fi',
'snippet': 'The Next, Next Generation tablet.'},
{'name': 'MOTOROLA XOOM',
'snippet': 'The Next, Next Generation tablet.'}
];
});

Aqu declaramos un controlador llamado PhoneListCtrl y lo registramos en el mdulo de AngularJS, phonecatApp.


Ten en cuenta que la directiva ng-app (en la etiqueta <html> ) especifica el nombre del mdulo phonecatApp como el
mdulo de carga cuando se inicia la aplicacin. Aunque el controlador no est haciendo mucho, juega un papel crucial.
Proporcionando el contexto para nuestro modelo de datos, el controlador nos permite establecer el data binding entre el
modelo y la vista.
La Directiva de ngController , situado en la etiqueta <body> , hace referencia a nuestro controlador PhoneListCtrl
2 - Template Angular

13

AngularJS

(ubicado en el archivo de JavaScript controllers.js).


El controlador PhoneListCtrl conecta los datos del array phone al $scope que fue inyectado en nuestra funcin
controller.

Scope
El concepto de Scope en Angular es crucial. El scope conecta la vista con el modelo. Angular usa el scope junto con la
informacin contenida en la vista, el modelo y el controlador, para mantener el modelo y la vista separada pero
sincronizada, cualquier cambo en el modelo es reflejada en la vista y viceversa.
Para aprender ms sobre Scopes puedes leer la documentacin de scope.

Test
La forma en que Angular separa el controlador de la vista, facilita el desarrollo del codigo de prueba. Si el controlador est
disponible en el espacio de nombres global entonces nosotros podramos simplemente instanciarlo con un objeto scope
simulado:

describe('PhoneListCtrl', function(){
it('should create "phones" model with 3 phones', function() {
var scope = {},
ctrl = new PhoneListCtrl(scope);
expect(scope.phones.length).toBe(3);
});
});

La prueba crea una instancia de PhoneListCtrl y verifica que la propiedad array phones en el contenga tres registros.
Este ejemplo muestra lo fcil que es crear una prueba unitaria en Angular. Puesto que la prueba es una parte crtica del
desarrollo de software, hacemos que sea fcil crear pruebas en Angular para que los desarrolladores se animen a codear.

Escribiendo y corriendo el test


Los desarrolladores de Angular prefieren la sintaxis del framework de desarrollo basada en Jasmine Desarrollo Dirigido por
Comportamientos (Behavior Driven Development) para realizar pruebas unitarias (Unit Testing) de cdigo JavaScript.
cuando estn escribiendo tests.
Aunque Angular no requieren uso de Jasmine, escribiremos todas las pruebas de este tutorial en Jasmine.
Tambin puedes aprender acerca de Jasmine en la pgina oficial de Jasmine. El proyecto angular-sedd est
preconfigurado para ejecutar pruebas unitarias usando Karma pero tienes que asegurarte de que Karma y sus plugins
necesarios esten instalados. Puedes hacer esto mediante la ejecucin del comando: npm install
Para ejecutar las pruebas y ver luego los cambios de archivos ejecuta: npm test
Karma iniciar automticamente una nueva instancia del navegador Chrome.
Puedes ignorarlo y dejarlo funcionando en segundo plano. Karma utiliza el navegador para ejecutar la prueba. Deberas
ver lo siguiente en la terminal:

2 - Template Angular

14

AngularJS

Karma server started at http://localhost:9876/ info (launcher): Starting browser "Chrome" info (Chrome 22.0):
Connected on socket id tPUm9DXcLHtZTKbAEO-n Chrome 22.0: Executed 1 of 1 SUCCESS (0.093 secs / 0.004 secs)

Pasaste la prueba, o no?


Para ejecutar las pruebas, slo debes cambiar la fuente de archivos .js de prueba. Karma nota el cambio y vuelve a
ejecutar las pruebas por usted.

Experimentos
Aade esta linea de cdigo: <p>Total number of phones: {{phones.length}}</p> Crear una nueva propiedad de modelo del
controlador y unelo a la vista. Por ejemplo: $scope.name = "World"; Ahora agrega esta linea al archivo index.html:
<p>Hello, {{name}}!</p> Actualiza el explorador y verifica que diga "Hola, mundo!". La actualizacin de la prueba unitaria

para el controlador ./test/unit/controllersSpec.js reflejar el cambio anterior. Ahora aade por ejemplo:
expect(scope.name).toBe('World');

Luego crea un repetidor en index.html que construya una tabla sencilla como esta:

<table>
<tr><th>row number</th></tr>
<tr ng-repeat="i in [0, 1, 2, 3, 4, 5, 6, 7]"><td>{{i}}</td></tr>
</table>

Ahora incrementa la lista de uno en uno:

<table>
<tr> <th>row number</th> </tr>
<tr ng-repeat="i in [0, 1, 2, 3, 4, 5, 6, 7]"><td>{{i+1}}</td></tr>
</table>

Punto extra: probar hacer una tabla de 8 x 8 utilizando una directiva ng-repeat adicional.

2 - Template Angular

15

AngularJS

Filtrando Repetidores
Hasta aqui hicimos un gran trabajo estableciendo la base para nuestra aplicacin, en el ltimo paso. Ahora vamos a hacer
algo mas simple; vamos a aadir una caja bsqueda de texto (s, ser sencillo!).
Tambin vamos a hacer escribir un test e2e porque es una buena practica y rapidamente detectar errores. Ejecuta esta
setencia en la terminal: git checkout -f step-3
Ahora la app tiene una caja de bsqueda. Observa que la lista de telfonos en la pgina cambia dependiendo de la
bsqueda que realice el usuario.

Controlador
No haremos cambios en el controlador.

Template
app/index.html:

<div class="container-fluid">
<div class="row">
<div class="col-md-2">
<!--Sidebar content-->
Search: <input ng-model="query">
</div>
<div class="col-md-10">
<!--Body content-->
<ul class="phones">
<li ng-repeat="phone in phones | filter:query">
{{phone.name}}
<p>{{phone.snippet}}</p>
</li>
</ul>
</div>
</div>
</div>

Hemos aadido una etiqueta <input> y usado la funcin filtro para procesar la directiva de ngRepeat, en la etiqueta input.
Esto le permite al usuario ingresar criterios de bsqueda y ver inmediatamente los efectos de su bsqueda en la lista de
telfonos.
Este nuevo cdigo se muestra a continuacin:
Data-Binding: Es una de las caractersticas ms importantes de AngularJS. Cuando la pgina carga, Angular une el
nombre que coloca el usuario en la caja de bsqueda a la variable del mismo nombre en el modelo de datos y los
mantiene a los dos en sincrona.
En este cdigo, la data que un usuario escribe en el input de busqueda query est inmediatamente disponibles en el filtro
del listado .
Cuando el modelo query cambi, causa que el string por el que esta filtrando el repetidor de la lista ng-repeat , actualice
el DOM de manera eficientemente para reflejar el estado actual del modelo.
3 - Filtrando Repetidores

16

AngularJS

Uso del filtro filter: la funcin del filtro es utilizar el valor de la consulta para crear un nuevo array que contenga slo los
registros que coincidan con la query .
ng-Repeat actualiza automticamente la vista en respuesta a la cantidad de telfonos devueltos por el filtro. El proceso es

totalmente transparente para el desarrollador.

Test
En el segundo paso hemos aprendido a cmo escribir y ejecutar pruebas unitarias. Las pruebas unitarias son perfectas
para probar controladores y otros componentes de nuestra aplicacin escrita en JavaScript, pero no pueden fcilmente
probar el cableado de nuestra aplicacin o la manipulacin del DOM. Para stos fines, una prueba de extremo a extremo
es una mucho mejor opcin.
La funcin de bsqueda fue totalmente implementada a travs de vistas y binding, por lo que vamos a escribir nuestra
primera prueba end-to-end, para comprobar que esta caracterstica funciona .
test/e2e/scenarios.js:

describe('PhoneCat App', function() {


describe('Phone list view', function() {
beforeEach(function() {
browser.get('app/index.html');
});

it('should filter the phone list as a user types into the search box', function() {
var phoneList = element.all(by.repeater('phone in phones'));
var query = element(by.model('query'));
expect(phoneList.count()).toBe(3);

3 - Filtrando Repetidores

17

AngularJS

query.sendKeys('nexus');
expect(phoneList.count()).toBe(1);
query.clear();
query.sendKeys('motorola');
expect(phoneList.count()).toBe(2);
});
});
});
`

Esta prueba comprueba que la caja de busqueda y el repetidor estn conectados correctamente. Observe lo fcil que es
escribir pruebas end-to-end en Angular. Aunque este ejemplo es para una prueba simple, es realmente fcil crear
cualquier prueba funcional end-to-end.

Corriendo pruebas end-to-end con Protractor


Aunque la sintaxis de esta prueba se parece mucho a la prueba de unitaria del controlador con Jasmine, la prueba de endto-end utiliza APIs de protractor. Lee acerca de las API de protractor en http://angular.github.io/protractor/#/api.

Experimentos
Al igual que usamos Karma para correr las pruebas unitarias, utilizamos Protractor para ejecutar pruebas end-to-end.

describe('PhoneCat App', function() {


describe('Phone list view', function() {
beforeEach(function() {
browser.get('app/index.html');
});
var phoneList = element.all(by.repeater('phone in phones'));
var query = element(by.model('query'));
it('should filter the phone list as a user types into the search box', function() {
expect(phoneList.count()).toBe(3);
query.sendKeys('nexus');
expect(phoneList.count()).toBe(1);
query.clear();
query.sendKeys('motorola');
expect(phoneList.count()).toBe(2);
});
it('should display the current filter value in the title bar', function() {
query.clear();
expect(browser.getTitle()).toMatch(/Google Phone Gallery:\s*$/);
query.sendKeys('nexus');
expect(browser.getTitle()).toMatch(/Google Phone Gallery: nexus$/);
});
});
});
`

Corre Protractor npm run protractor para ver como falla.


Ahora agrega el siguiente codigo: <title>Google Phone Gallery: {{query}}</title> .
Sin embargo, al volver a cargar la pgina, no aparece el resultado esperado. Esto es porque el modelo de "consulta" se
encuentra en el body que es el mbito definido por el controlador ng-controller="PhoneListCtrl"
3 - Filtrando Repetidores

18

AngularJS

Si desea enlazar el modelo de consulta de la etiqueta


Asegrese de remover la declaracin de ng-controler del body. Vuelve a correr npm run protractor para ver que pase el
test.

3 - Filtrando Repetidores

19

AngularJS

Enlace de datos bidireccional


En este paso, agregaremos una caracterstica para permitir a los usuarios controlar el orden de los elementos de la lista de
telfonos. El orden dinmico se implementa creando una nueva propiedad del modelo, conectada junto con el repetidor y
dejando que la magia de data binding haga el resto del trabajo. Adems de la caja de bsqueda, la aplicacin muestra un
men que permite a los usuarios controlen el orden en que aparecen los telfonos.
Ejecuta este comando: git checkout -f step-4

Template
app/index.html:

Search: <input ng-model="query">


Sort by:

<select ng-model="orderProp">
<option value="name">Alphabetical</option>
<option value="age">Newest</option>
</select>
<ul class="phones">
<li ng-repeat="phone in phones | filter:query | orderBy:orderProp">
<span>{{phone.name}}</span>
<p>{{phone.snippet}}</p>
</li>
</ul>

Realizamos los siguientes cambios a la plantilla index.html


En primer lugar, hemos aadido en la etiqueta HTML <select> un elemento llamado orderProp, para que nuestros
usuarios pueden escoger entre las dos opciones proporcionadas.

4 - Enlace de Datos Bidireccional

20

AngularJS

Angular crea doble binding entre el elemento select y el modelo de orderProp. OrderProp es utilizada como entrada
para el filtro de orderBy.
Como ya mencionamos en la seccin de enlace de datos (data binding) y el repetidor en el paso 3, cuando el modelo
cambia (por ejemplo porque un usuario cambia el orden cuando selecciona el men desplegable), el enlace de datos de
Angular har que la vista se actualice automticamente No es necesario manipular el DOM.

Controlador
app/js/controllers.js:

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


phonecatApp.controller('PhoneListCtrl', function ($scope) {
$scope.phones = [
{'name': 'Nexus S',
'snippet': 'Fast just got faster with Nexus S.',
'age': 1},
{'name': 'Motorola XOOM with Wi-Fi',
'snippet': 'The Next, Next Generation tablet.',
'age': 2},
{'name': 'MOTOROLA XOOM',
'snippet': 'The Next, Next Generation tablet.',
'age': 3}
];
$scope.orderProp = 'age';
});

Hemos modificado el modelo phones - la gama de telfonos - y aadimos una propiedad edad a cada registro del telfono.
Esta propiedad se utiliza para clasificar los telefonos por edad. Hemos aadido una lnea al controlador que establece el
valor predeterminado de orderProp a la edad. Si no hubiramos establecido este valor por defecto aqu, el filtro de orderBy
no se inicializaria hasta que el usuario seleccione una opcin en el men desplegable.

4 - Enlace de Datos Bidireccional

21

AngularJS

Este es un buen momento para hablar del enlace de datos bidireccional (two-way data biding). Observe que cuando la
aplicacin se carga en el navegador, el valor de menor edad queda seleccionado. Esto es porque hemos creado orderProp
para la 'edad' en el controlador. As que el enlace funciona en la direccin de nuestro modelo para la interfaz de usuario.
Ahora si seleccionas "Orden alfabtico" en el men desplegable, tambin se actualizar el modelo y los telfonos se
reordenaran. El enlace de datos est haciendo su trabajo en la direccin opuesta, desde la interfaz de usuario al modelo.

Test
Las modificaciones deben ser verificadas con una prueba unitaria y un test de end-to-end. Ejecuta el test:
test/unit/controllersSpec.js:

describe('PhoneCat controllers', function() {


describe('PhoneListCtrl', function(){
var scope, ctrl;
beforeEach(module('phonecatApp'));
beforeEach(inject(function($controller) {
scope = {};
ctrl = $controller('PhoneListCtrl', {$scope:scope});
}));
it('should create "phones" model with 3 phones', function() {
expect(scope.phones.length).toBe(3);
});

it('should set the default value of orderProp model', function() {


expect(scope.orderProp).toBe('age');
});
});
});

Ahora debera ver la siguiente salida en la pestaa de Karma: Chrome 22.0: Executed 2 of 2 SUCCESS (0.021 secs / 0.001
secs)

Ahora nos centraremos en la prueba end-to-end test/e2e/scenarios.js:

it('should be possible to control phone order via the drop down select box', function() {
var phoneNameColumn = element.all(by.repeater('phone in phones').column('phone.name'));
var query = element(by.model('query'));
function getNames() {
return phoneNameColumn.map(function(elm) {
return elm.getText();
});
}
query.sendKeys('tablet'); //let's narrow the dataset to make the test assertions shorter
expect(getNames()).toEqual([
"Motorola XOOM\u2122 with Wi-Fi",
"MOTOROLA XOOM\u2122"
]);
element(by.model('orderProp')).element(by.css('option[value="name"]')).click();
expect(getNames()).toEqual([
"MOTOROLA XOOM\u2122",
"Motorola XOOM\u2122 with Wi-Fi"
]);
});...

4 - Enlace de Datos Bidireccional

22

AngularJS

La prueba end-to-end verifica que el orden de la caja de seleccin est trabajando correctamente.
Ahora puede volver a ejecutar npm run protractor para ver que las pruebas que se ejecutan.

Experimentos
En el controlador PhoneListCtrl , quita la instruccin que establece el valor de orderProp y vers que Angular
temporalmente agregar una nueva opcin en blanco ("desconocida") a la lista desplegable y el orden por defecto
cambiar.
Agregar un enlace de en la plantilla index.html para ver su valor actual como texto. Invierte el orden de clasificacin
mediante la adicin de un - smbolo antes del valor de: <option value="-age">Oldest</option>

4 - Enlace de Datos Bidireccional

23

AngularJS

XHRs e inyeccin de dependencias


Ya no construiremos nuestra aplicacin slo con tres telfonos. Vamos a buscar un conjunto de datos un poco ms grande
de nuestro servidor mediante el uso uno de los servicios integrados de Angular llamado $http . Vamos a utilizar inyeccin
de dependencias de Angular (DI) para proporcionar el servicio al controlador PhoneListCtrl . Ahora hay una lista de 20
telfonos, cargados desde el servidor. Ahora ejecuta este comando: git checkout -f step-5

Datos
El archivo app/phones/phones.json de tu proyecto es un conjunto de datos que contiene una lista ms grande de telfonos
almacenada en formato JSON. Lo siguiente es una muestra del archivo:

[
{
"age": 13,
"id": "motorola-defy-with-motoblur",
"name": "Motorola DEFY\u2122 with MOTOBLUR\u2122",
"snippet": "Are you ready for everything life throws your way?"
...
},
...
]

Controlador
Vamos a usar el servicio de $http de Angular en nuestro controlador para realizar una peticin HTTP al servidor web para
recuperar los datos que estn en el archivo app/phones/phones.json .
Los servicios son gestionados por el subsistema de DI (Inyeccin de Dependencias) de Angular. La Inyeccin de
dependencia ayuda a que tus aplicaciones web estn bien estructuradas y acopladas. app/js/controllers.js :

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


phonecatApp.controller('PhoneListCtrl', function ($scope, $http) {
$http.get('phones/phones.json').success(function(data) {
$scope.phones = data;
});
$scope.orderProp = 'age';
});

El servicio $http hace una peticin GET a nuestro servidor web, pidiendo el archivo phones/phones.json (la url es relativa
a nuestro archivo index.html). El servidor responde con los datos en un archivo json. La respuesta tambin puede haber
sido generada dinmicamente por un servidor de back-end. El navegador y nuestra aplicacin tienen el mismo aspecto.
Por una cuestin de simplicidad utilizaremos un archivo json en este tutorial.
El servicio de $http devuelve un objeto promise con el mtodo success. Llamamos a este mtodo para controlar la
respuesta asincrnica y asignar los datos del telfono al mbito controlado por este controlador, como un modelo llamado
telfonos. Angular detecta la respuesta json y la analiza por nosotros!
Para utilizar este servicio de Angular, simplemente declaras los nombres de las dependencias que necesita como
argumentos para la funcin constructora del controlador:

5 - XHRs e Inyeccion de Dependencias

24

AngularJS

phonecatApp.controller('PhoneListCtrl', function ($scope, $http) {...}

$ Convencin de nombres de los prefijos


Puedes crear tus propios servicios y, de hecho, eso es precisamente lo que haremos en el paso 11. Como una convencin
de nomenclatura, los servicios integrados de Angular, el alcance mtodos y algunas otras api de Angular tienen el prefijo $
delante del nombre.
El prefijo $ es un espacio de nombres reservado para los servicios de Angular. Para evitar errores es mejor evitar los
nombres y modelos que empiecen con un $. Si inspeccionas un Scope, tambin puedes notar que algunas propiedades
comienzan con $$. Estas propiedades son consideradas privadas y no deberan acceder a ellas o modificarlas.

Una nota sobre Minification


Debido a que Angular infiere las dependencias desde los nombres de los argumentos del constructor del controlador, si
minificaras el codigo del PhoneListCtrl, todos los argumentos de las funciones tambin se minificaran, y el inyector de
dependencias no sera capaz de identificar los servicios correctamente.
Podemos superar este problema de anotacin de la funcin con los nombres de las dependencias provistas como strings
que como sabemos no son modificadas en el proceso de minificado. Hay dos maneras pasar estas anotaciones al
inyector:
Crea una propiedad $inject en el controlador que contiene un array de strings cada string en el array es el nombre de

5 - XHRs e Inyeccion de Dependencias

25

AngularJS

un servicio a inyectar por el parametro correspondiente.


En nuestro ejemplo nosotros escribiramos:

function PhoneListCtrl($scope, $http) {...}


PhoneListCtrl.$inject = ['$scope', '$http'];
phonecatApp.controller('PhoneListCtrl', PhoneListCtrl);

Usa una anotacin en linea donde, en vez de simplemente proveer una funcin vas a proveer un array. Este array
contiene una lista de nombres de servicio seguidas por la funcin.

function PhoneListCtrl($scope, $http) {...}


phonecatApp.controller('PhoneListCtrl', ['$scope', '$http', PhoneListCtrl]);

Ambos mtodos funcionan si la funcin puede ser inyectada por Angular, est en vos usar el estilo que quieras en tus
proyecto.
Cuando estamos usando el segundo mtodo, es comn proveer el constructor en lnea cmo una funcin annima cuando
creamos el controlador:

phonecatApp.controller('PhoneListCtrl', ['$scope', '$http', function($scope, $http) {...}]);

De aqu en adelante, vamos a usar el mtodo en linea . Con esto en mente, pasemos las anotaciones a nuestro
PhoneListCtrl :

PhoneListCtrl: app/js/controllers.js:

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


phonecatApp.controller('PhoneListCtrl', ['$scope', '$http',
function ($scope, $http) {
$http.get('phones/phones.json').success(function(data) {
$scope.phones = data;
});
$scope.orderProp = 'age';
}]);

Test
test/unit/controllersSpec.js :

Ya que hemos utilizado inyeccin de dependencias y nuestro controlador tiene dependencias, construir el controlador en
nuestro test es un poco ms complicado.
Podramos utilizar el operador new y proveer una implementacin falsa $htttp como sea Angular provee de un servicio
mock $http que podemos usar en nuestros test unitarios.
Configuramos las respuestas "falsas" a las peticiones del servidor llamando mtodos en un servicio llamado $httpBackend :

describe('PhoneCat controllers', function() {


describe('PhoneListCtrl', function(){

5 - XHRs e Inyeccion de Dependencias

26

AngularJS

var scope, ctrl, $httpBackend;


// Load our app module definition before each test.
beforeEach(module('phonecatApp'));
// The injector ignores leading and trailing underscores here (i.e. _$httpBackend_).
// This allows us to inject a service but then attach it to a variable
// with the same name as the service in order to avoid a name conflict.
beforeEach(inject(function(_$httpBackend_, $rootScope, $controller) {
$httpBackend = _$httpBackend_;
$httpBackend.expectGET('phones/phones.json').
respond([{name: 'Nexus S'}, {name: 'Motorola DROID'}]);
scope = $rootScope.$new();
ctrl = $controller('PhoneListCtrl', {$scope: scope});
}));

Ahora vamos a realizar aseveraciones para verificar que los modelo telfonos no existe en el scope antes de que la
respuesta sea recibida:

it('should create "phones" model with 2 phones fetched from xhr', function() {
expect(scope.phones).toBeUndefined();
$httpBackend.flush();
expect(scope.phones).toEqual([{name: 'Nexus S'},
{name: 'Motorola DROID'}]);
});

Vaciamos la cola de peticiones en el navegador llamando el metodo $httpBackend.flush(). Esto hace que la promise
retornada por el servicio http sea resuelta con la respuesta esperada. Finalmente verificamos el valor por defecto de
orderProp este seteado correctamente:

it('should set the default value of orderProp model', function() {


expect(scope.orderProp).toBe('age');
});

Ahora nosotros veremos el siguiente output de resultado de Karma:


Chrome 22.0: Executed 2 of 2 SUCCESS (0.028 secs / 0.007 secs)

Experimentos:
Al final de index.html, agreg: <pre>{{phones | filter:query | orderBy:orderProp | json}}</pre>
Y en el PhoneListCtrl procesa la respuesta http limitando el numero de telefonos a los 5 primeros en la lista. Para hacer
eso usa el siguiente codigo en el $http callback: $scope.phones = data.splice(0, 5);

5 - XHRs e Inyeccion de Dependencias

27

AngularJS

Modelado de imagenes y enlaces


En este paso, veremos como agregar imagenes miniatura y enlaces que, por ahora, no nos llevarn a ningun lado.

Datos
Notaras que el archivo phones.json contiene un ID y una imagen para cada telfono. El URL apunta al directorio
app/img/phones/.

[
{
...
"id": "motorola-defy-with-motoblur",
"imageUrl": "img/phones/motorola-defy-with-motoblur.0.jpg",
"name": "Motorola DEFY\u2122 with MOTOBLUR\u2122",
...
},
...
]

Template
En los atributos href utilizamos, las ya conocidas, {{ }} (doble-llaves) . En la segunda parte, agregamos el nombre del
telefono , y hacemos lo mismo con el ID .
Utilizaremos la directiva ng-src que previene al navegador de que estamos utilizando Angular para que no nos lleve a una
direccion invalida.

<ul class="phones">
<li ng-repeat="phone in phones | filter:query | orderBy:orderProp" class="thumbnail">
<a href="#/phones/{{phone.id}}" class="thumb"><img ng-src="{{phone.imageUrl}}"></a>
<a href="#/phones/{{phone.id}}">{{phone.name}}</a>
<p>{{phone.snippet}}</p>
</li>
</ul>

Testing
Aplicamos una verificacin End-To-End para ver que los enlaces e imagenes se esten cargando de la manera correcta

it('should render phone specific links', function() {


var query = element(by.model('query'));
query.sendKeys('nexus');
element.all(by.css('.phones li a')).first().click();
browser.getLocationAbsUrl().then(function(url) {
expect(url).toBe('/phones/nexus-s');
});
});

6 - Modelando Imagenes y Enlaces

28

AngularJS

Ruteo y vistas mltiples


En este capitulo veremos como crear un diseo y aadir distintas vistas mediante el mdulo ngRoute de AngularJS.

Dependencias
La funcionabilidad del ruteo se provee via ngRoute, un mdulo que viene por separado de Angular.
Utilizaremos Bower para instalar las dependendencias. En este paso, el archivo bower.json ser actualizado, incluyendo
nuestra nueva dependencia.

{
"name": "angular-phonecat",
"description": "A starter project for AngularJS",
"version": "0.0.0",
"homepage": "https://github.com/angular/angular-phonecat",
"license": "MIT",
"private": true,
"dependencies": {
"angular": "1.4.x",
"angular-mocks": "1.4.x",
"jquery": "~2.1.1",
"bootstrap": "~3.1.1",
"angular-route": "~1.4.0"
}
}

La dependencia "angular-route": "~1.4.0" le dice a Bower, que instale el componente ng-Route, de tal manera que sea
compatible con la version 1.4.x.

Vistas multiples, ruteo y nuestro diseo


Nuestra aplicacin de a poco se va convirtiendo ms compleja. Durante los pasos anteriores, la app provea a nuestros
usarios una vista simple(la lista de todos nuestros telfonos), y todo el cdigo que estaba provisto en el archivo
index.html. El proximo paso es crear una vista en la cual se muestre, en detalle, la informacin de cada dispositivo de
nuestra lista.
Para aadir una vista detallada, podriamos expandir el archivo index.html para que contenga el codigo de las dos vistas,
pero se pondra complicado en pocos minutos. En cambio, vamos a transformar el index.html en una diseo que ser
comn para todas las vistas de nuestra aplicacin. Luego, agregaremos otras "diseos parciales" a nuestro diseo
principal, dependiendo de la ruta o vista en la cual se encuentre el usuario.
La ruta se provee mediante $routeProvider, quien es el proveedor del servicio $route. Este servicio hace que sea facil
conectar controladores, diseos/plantillas y saber donde esta la vista del navegador. Utilizando esto, nos permite saber el
historial y los marcadores del navegador.

Nota sobre los DI y Providers


DI(Injectores de dependencia) es un mdulo de Angular, por lo cual debemos saber algunas cosas de como funciona.
Cuando la aplicacion comienza, Angular crea un injector que sirve para encontrar e injectar todos los servicios que son
requeridos en tu app. El injector, por si mismo, no conoce nada acerca de los servicios $http o $route. De hecho, no
conoce si quiese sobre la existencia de los mismos al menos que sean configurados en sus mdulos

7 - Ruteo y Vistas Multiples

29

AngularJS

Providers son objetos que proveen instacias de serivicios y exponen configuraciones de APIs que pueden ser usadas
para controlar la creacion y problemas de un servicio. En el caso del $route, el $routeProvider expone APIs que te
permiten definir rutas para tu aplicacion.
Los modulos de Angular resuelven problemas al remover estados globales de la aplicacion y proveer una manera de
configurar tu injector.
Para entender un poco mas acerca de el DI de Angular, vistit Entendiendo la Injeccion de Dependencia (en ingles)

Plantillas
El servicio de $route es usualmente usado en conjunto con la direccitiva ngView. El rol de ngView es incluir la vista actual
para la ruta dentro del diseo. Esto hace que encaje perfecto con nuestro template index.html.
A partir de la version 1.2 de Angular, el modulo ngRoute viene por separado y debe ser cargado como archivo angularroute.js.


...






Incluimos dos nuevos elementos script que hacen referencia a dos nuevos archivos:
angular-route.js --> Define el modulo ngRoute para Angular
app.js --> El archivo que contiene el modulo principal de nuestra app
Ahora, debemos remover casi todo lo que tiene nuestro archivo index.html(de manera que quede como el codigo de
arriba) y todo lo que sacamos, lo enviaremos a un nuevo archivo, llamado phone-list.html.
Debe estar almacenado en app/partials/...

<div class="container-fluid">
<div class="row">
<div class="col-md-2">
<!--Sidebar content-->
Search: <input ng-model="query">
Sort by:
<select ng-model="orderProp">
<option value="name">Alphabetical</option>
<option value="age">Newest</option>
</select>
</div>
<div class="col-md-10">
<!--Body content-->
<ul class="phones">

7 - Ruteo y Vistas Multiples

30

AngularJS

<li ng-repeat="phone in phones | filter:query | orderBy:orderProp" class="thumbnail">


<a href="#/phones/{{phone.id}}" class="thumb"><img ng-src="{{phone.imageUrl}}"></a>
<a href="#/phones/{{phone.id}}">{{phone.name}}</a>
<p>{{phone.snippet}}</p>
</li>
</ul>
</div>
</div>
</div>

Tambien aadimos un template para la vista con los detalles llamada phone-detail.html y lo guardamos en
app/partials/...

TBD: detail view for <span>{{phoneId}}</span>

En este template, utilizamos el phoneId que declaramos en el controlador PhoneDetailCtrl.

El mdulo de app.js
Para mejorar la organizacin de la aplicacin vamos a utilizar el mdulo de Angular, ngRoute y vamos a mover los
controladores a nuestro propio mdulo phonecatControllers(como vern ahora).
Agregamos el archivo angular-route.js a nuestro index.html y creamos un nuevo modulo phocatControllers en
controllers.js. Sin embargo, eso no es todo lo que necesitamos para ser poder utilizar su cdigo. Necesitamos aadir los
mdulos como una dependencia a nuestra app. Listando estos dos mdulos como dependencias de phonecatApp,
podemos usar las directivas y servicios que proveen.
app/js/app.js

var phonecatApp = angular.module('phonecatApp', [


'ngRoute',
'phonecatControllers'
]);
...

En el segundo argumento, creamos un array con los mdulos de los cuales phonecatApp depende.

...
phonecatApp.config(['$routeProvider',
function($routeProvider) {
$routeProvider.
when('/phones', {
templateUrl: 'partials/phone-list.html',
controller: 'PhoneListCtrl'
}).
when('/phones/:phoneId', {
templateUrl: 'partials/phone-detail.html',
controller: 'PhoneDetailCtrl'
}).
otherwise({
redirectTo: '/phones'
});
}]);

Utilizando el metodo phonecatApp.config(), requerimos al $routeProvider que se injecte a nuestra funcion de


configuracion y use el metodo $routeProvider.when() para definir nuestras rutas.
Nuestras rutas de aplicacion estan definidas as:
7 - Ruteo y Vistas Multiples

31

AngularJS

when('/phones'): se muestra la lista de dispositivos cuando la URL esta posicionada en /phones. Para crear esta
vista, Angular mostrar el diseo de phone-list.html y el controlador PhoneListCtrl.
when('/phones/:phonesId'): se muestran los detalles del dispostivo cuando la URL este posicionada en
/phones/:phoneId, donde :phoneId es una variable de la URL. Para crear y mostrar esta vista, Angular usar el diseo
de phone-detail.html y el controlador PhoneDetailCtrl.
otherwise({redirectTo:'/phones'}): redirecciona hacia /phones cuando no encuentra lo que busca.
Otra vez, not que creamos un nuevo mdulo llamado phonecatControllers. Para aplicaciones pequeas de Angular, es
comn crear solo un mdulo para todos tus controladores, si son pocos. A medida que nuestra aplicacin crezca, es mejor
separar el cdigo en diferentes mdulos. Para aplicaciones ms y ms grandes, debers crear mdulos separados para
mejorar el rendimiento de tu app.
Debido a que nuestra app es pequea, solo aadiremos nuestros controladores a phonecatControllers.

Testing
Para saber que todo funciona bien, ejecuta el siguiente codigo Ent-To-End.

...
it('should redirect index.html to index.html#/phones', function() {
browser.get('app/index.html');
browser.getLocationAbsUrl().then(function(url) {
expect(url).toEqual('/phones');
});
});
describe('Phone list view', function() {
beforeEach(function() {
browser.get('app/index.html#/phones');
});
...
describe('Phone detail view', function() {
beforeEach(function() {
browser.get('app/index.html#/phones/nexus-s');
});

it('should display placeholder page with phoneId', function() {


expect(element(by.binding('phoneId')).getText()).toBe('nexus-s');
});
});

7 - Ruteo y Vistas Multiples

32

AngularJS

Ms y ms templates
En este paso implementaremos la vista de detalles del dispositivo cuando el usuario aprete en el telefono deseado.

Datos
Ademas del phones.json, dentro de app/phones, encontraremos un archivo para cada telefono con las especificaciones
del mismo.
app/phones/nexus-s.json (ejemplo)

{
"additionalFeatures": "Contour Display, Near Field Communications (NFC), ...",
"android": {
"os": "Android 2.3",
"ui": "Android"
},
...
"images": [
"img/phones/nexus-s.0.jpg",
"img/phones/nexus-s.1.jpg",
"img/phones/nexus-s.2.jpg",
"img/phones/nexus-s.3.jpg"
],
"storage": {
"flash": "16384MB",
"ram": "512MB"
}
}

Controladores
Expandiremos PhoneDetailCtrl usando el servicio $http para obtener archivos del tipo JSON. Esto funciona de la misma
manera que el controlador de la lista de telefonos.
app/js/controllers.js

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


phonecatControllers.controller('PhoneDetailCtrl', ['$scope', '$routeParams', '$http',
function($scope, $routeParams, $http) {
$http.get('phones/' + $routeParams.phoneId + '.json').success(function(data) {
$scope.phone = data;
});
}]
);

Para constuir la URL para la peticin HTTP, usamos $routeParams.phoneId que extrameos de la ruta actual brindada
por $route.

Plantilla
La linea con el elemento span que hicimos en el phone-detail.html la cambiaremos por las expresiones {{}} .
app/partials/phone-detail.html
8 - Ms y ms Templates

33

AngularJS

<img ng-src="{{phone.images[0]}}" class="phone">


<h1>{{phone.name}}</h1>
<p>{{phone.description}}</p>
<ul class="phone-thumbs">
<li ng-repeat="img in phone.images">
<img ng-src="{{img}}">
</li>
</ul>
<ul class="specs">
<li>
<span>Availability and Networks</span>
<dl>
<dt>Availability</dt>
<dd ng-repeat="availability in phone.availability">{{availability}}</dd>
</dl>
</li>
...
<li>
<span>Additional Features</span>
<dd>{{phone.additionalFeatures}}</dd>
</li>
</ul>

Testing
Ejecutaremos este cdigo, el cual es parecido al que hicimos en el captulo 5.
test/unit/controllersSpec.js

...
describe('PhoneDetailCtrl', function(){
var scope, $httpBackend, ctrl;
beforeEach(inject(function(_$httpBackend_, $rootScope, $routeParams, $controller) {
$httpBackend = _$httpBackend_;
$httpBackend.expectGET('phones/xyz.json').respond({name:'phone xyz'});
$routeParams.phoneId = 'xyz';
scope = $rootScope.$new();
ctrl = $controller('PhoneDetailCtrl', {$scope: scope});
}));

it('should fetch phone detail', function() {


expect(scope.phone).toBeUndefined();
$httpBackend.flush();
expect(scope.phone).toEqual({name:'phone xyz'});
});
});
...

Ahora, deberias de ver el siguiente mensaje en la tabla de Karma

Chrome 22.0: Executed 3 of 3 SUCCESS (0.039 secs / 0.012 secs)

Tambin aadimos un cdigo End-To-End que navega por los detalles del Nexus S y verifica que el encabezado (heading)
de la pgina es "Nexus S".

8 - Ms y ms Templates

34

AngularJS

test/e2e/scenarios.js

...
describe('Phone detail view', function() {
beforeEach(function() {
browser.get('app/index.html#/phones/nexus-s');
});

it('should display nexus-s page', function() {


expect(element(by.binding('phone.name')).getText()).toBe('Nexus S');
});
});
...

8 - Ms y ms Templates

35

AngularJS

Filtros
En este captulo, veremos como crear nuestro propio filtro para el template.

Filtros personalizados
Para crear un nuevo filtro, crearermos un mdulo phonecatFilters y registra tu filtro personalizado con este cdigo:
app/js/filters.js

angular.module('phonecatFilters', []).filter('checkmark', function() {


return function(input) {
return input ? '\u2713' : '\u2718';
};
});

El nombre de nuestro filtro es "checkmarck". El input evalua si es verdadero o falso y devuelve uno de los dos
caracretres:
true -> '\u2713'
false -> '\u2718'
Ahora que nuestro filtro esta listo, necesitamos registrar el mdulo phonecatFiltrers como una dependencia de nuestro
mdulo principal phonecatApp.
app/js/app.js

...
angular.module('phonecatApp', ['ngRoute','phonecatControllers','phonecatFilters']);
...

Template
Desde que creamos el archivo app/js/filter.js, necesitamos incliuirlo a nuestro diseo para que funcione.
app/index.html

...
<script src="js/controllers.js"></script>
<script src="js/filters.js"></script>
...

Para utilizar los filtros en Angular, la sintaxis es: {{ expression | filter }}


Ahora, implementaremos el filtro a nuestra plantilla phone-detail.html
app/partials/phone-detail.html

...
<dl>

9 - Filtros

36

AngularJS

<dt>Infrared</dt>
<dd>{{phone.connectivity.infrared | checkmark}}</dd>
<dt>GPS</dt>
<dd>{{phone.connectivity.gps | checkmark}}</dd>
</dl>
...

Prueba
Los filtros, como todo elemento, deben ser probados.
test/unit/filtersSpec.js

describe('filter', function() {
beforeEach(module('phonecatFilters'));
describe('checkmark', function() {
it('should convert boolean values to unicode checkmark or cross',
inject(function(checkmarkFilter) {
expect(checkmarkFilter(true)).toBe('\u2713');
expect(checkmarkFilter(false)).toBe('\u2718');
}));
});
});

Siempre debemos llamar a la funcin beforeEach(module('phonecatFilters')) antes que cualquier ejecucin de filtros. Esto
carga el mdulo phoneCatFilters dentro del injector para esta prueba.
La funcin ineject(function(checkmarckFileter){ ... }) ayuda a acceder a los filtros que queremos probar.
Ahora, deberias de ver el siguiente mensaje en la tabla de Karma

Chrome 22.0: Executed 4 of 4 SUCCESS (0.034 secs / 0.012 secs)

9 - Filtros

37

AngularJS

Controladores de eventos
En este paso, veremos como aadir una imagen que al apretarla, nos mostrar la pagina de detalles.

Controlador
app/js/controllers.js

...
var phonecatControllers = angular.module('phonecatControllers',[]);
phonecatControllers.controller('PhoneDetailCtrl', ['$scope', '$routeParams', '$http',
function($scope, $routeParams, $http) {
$http.get('phones/' + $routeParams.phoneId + '.json').success(function(data) {
$scope.phone = data;
$scope.mainImageUrl = data.images[0];
});
$scope.setImage = function(imageUrl) {
$scope.mainImageUrl = imageUrl;
};
}]);

En PhoneDetailCtrl creamos un propiedad modelo mainImageUrl y la seteamos al valor default de la URL de la primera
imagen del dispositivo.
Tambin creamos el envento setImage que cambiar el valor de mainImageUrl.

Template
app/partials/phone-detail.html

<img ng-src="{{mainImageUrl}}" class="phone">


...
<ul class="phone-thumbs">
<li ng-repeat="img in phone.images">
<img ng-src="{{img}}" ng-click="setImage(img)">
</li>
</ul>
...

El valor de la directiva ng-src de la imagen principal se la seteamos al valor de la propiedad mainImageUrl.


Tambien registramos un evento ng-click a imagenes miniaturas. Cuando el usuario aprete en una de las ellas, el
controlador usar el evento setImage para cambiar el valor de la propiedead mainImageUrl al URL de la imagen
miniatura.

Prueba
Para verificar estos dos nuevos elementos, aadimos dos codigos End-To-End. El primero verifica que la imagen principal
este establecida a la imagen principal por default. La segunda prueba realiza clicks en las imagenes miniaturas y verifica
que cambie la imagen principal.
10 - Controladores de Eventos

38

AngularJS

test/e2e/scenarios.js

...
describe('Phone detail view', function() {
...
it('should display the first phone image as the main phone image', function() {
expect(element(by.css('img.phone')).getAttribute('src')).toMatch(/img\/phones\/nexus-s.0.jpg/);
});

it('should swap main image if a thumbnail image is clicked on', function() {


element(by.css('.phone-thumbs li:nth-child(3) img')).click();
expect(element(by.css('img.phone')).getAttribute('src')).toMatch(/img\/phones\/nexus-s.2.jpg/);
element(by.css('.phone-thumbs li:nth-child(1) img')).click();
expect(element(by.css('img.phone')).getAttribute('src')).toMatch(/img\/phones\/nexus-s.0.jpg/);
});
});

test/unit/controllersSpec.js

...
beforeEach(module('phonecatApp'));
...
describe('PhoneDetailCtrl', function(){
var scope, $httpBackend, ctrl,
xyzPhoneData = function() {
return {
name: 'phone xyz',
images: ['image/url1.png', 'image/url2.png']
}
};

beforeEach(inject(function(_$httpBackend_, $rootScope, $routeParams, $controller) {


$httpBackend = _$httpBackend_;
$httpBackend.expectGET('phones/xyz.json').respond(xyzPhoneData());
$routeParams.phoneId = 'xyz';
scope = $rootScope.$new();
ctrl = $controller('PhoneDetailCtrl', {$scope: scope});
}));

it('should fetch phone detail', function() {


expect(scope.phone).toBeUndefined();
$httpBackend.flush();
expect(scope.phone).toEqual(xyzPhoneData());
});
});

10 - Controladores de Eventos

39

AngularJS

Rest y servicios personalizados


Dependencias
La funcionabilidad RESTful provista por Angular en el modelo ngResource, el cual viene aparte del modulo principal.
Utilizamos Bower para instalar las dependendencias. En este paso el archivo bower.json ser actualizado, incluyendo
nuestra nueva dependencia.
bower.json

"name": "angular-seed",
"description": "A starter project for AngularJS",
"version": "0.0.0",
"homepage": "https://github.com/angular/angular-seed",
"license": "MIT",
"private": true,
"dependencies": {
"angular": "~1.3.0",
"angular-mocks": "~1.3.0",
"bootstrap": "~3.1.1",
"angular-route": "~1.3.0",
"angular-resource": "~1.3.0"
}
}

La dependencia "angular-resource": "~1.3.0" le dice a Bower, que instale el componente ngResource, de tal manera que
sea compatible con la version 1.3.x.

Template
Nuestro servicio personalizado sera definido en el archivo app/js/services.js asi que necesitamos incluir el archivo en
nuestra estructura. Adicionalmente, tambien necesitamos cargar el archivo angular-resouce.js, que contiene el modulo
ngResource:
app/index.html

...
<script src="bower_components/angular-resource/angular-resource.js"></script>
<script src="js/services.js"></script>
...
- Servicio
Creamos nuestro propio servico para proveer acceso a los datos del dispositivo desde un serviodr
- app/js/services.js var phonecatServices = angular.module('phonecatServices', ['ngResource']);
phonecatServices.factory('Phone', ['$resource',
function($resource){
return $resource('phones/:phoneId.json', {}, {
query: {method:'GET', params:{phoneId:'phones'}, isArray:true}
});
}]);

Creamos un mdulo API para registrar nuestros servicios personalizados usando un metodo de "fabricacin". Utilizamos el
nombre del servicio ('Phone') y la funcin factory. Esta, es similar al constructor de un controlador en el caso de que
ambas dependencias deben ser injectadas mediante argumentos. El servicio Phone se declara una dependencia del

11 - REST y Servicios Personalizados

40

AngularJS

servicio $resource.
El servicio $resource hace fcil el crear un cliente RESTful con tan solo pocas lineas de cdigo. Esto puede despues ser
usado por aplicaciones, en vez del servicio $http.
app/js/app.js

...
angular.module('phonecatApp', ['ngRoute', 'phonecatControllers','phonecatFilters', 'phonecatServices']).
...

Necesitamos aadir el mdulo phonecatServices como dependencia de phonecatApp.

Controlador
Simplificamos nuestros sub-controladores (PhoneListCtrl y PhoneDetailCtrl) al factorizar el servicio $http, remplazandolo
con un nuevo servicio llamado Phone. El servicio $resource de Angular es mas fcil que usar $http para interactuar con
los datos RESTful. Tambin es ms fcil enterender el cdigo que nuestros controladores estan haciendo.
app/js/contoladores.js

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


...
phonecatControllers.controller('PhoneListCtrl', ['$scope', 'Phone', function($scope, Phone) {
$scope.phones = Phone.query();
$scope.orderProp = 'age';
}]);
phonecatControllers.controller('PhoneDetailCtrl', ['$scope', '$routeParams', 'Phone', function($scope, $routeParams, Phone) {
$scope.phone = Phone.get({phoneId: $routeParams.phoneId}, function(phone) {
$scope.mainImageUrl = phone.images[0];
});
$scope.setImage = function(imageUrl) {
$scope.mainImageUrl = imageUrl;
}
}]);

Not como, en PhoneListCtrl, remplazamos:

$http.get('phones/phones.json').success(function(data) {
$scope.phones = data;
});

con:

$scope.phones = Phone.query();

Esta, es una simple declaracin que usaremos para todos los telfonos de nuestra lista.

Testing
11 - REST y Servicios Personalizados

41

AngularJS

Ya que estamos utilzando el mdulo ngResource, es necesario actualizar nuestro archivo de configuracion de Karma con
nuestro recurso de Angular.
test/karma.conf.js

iles : [
'app/bower_components/angular/angular.js',
'app/bower_components/angular-route/angular-route.js',
'app/bower_components/angular-resource/angular-resource.js',
'app/bower_components/angular-mocks/angular-mocks.js',
'app/js/**/*.js',
'test/unit/**/*.js'
],

Hemos modificado nuestra prueba para verificar que nuestro nuevo servicio HTTP pida y procece de manera correcta.
Tambien controla que los controladores estan interactuando de manera correcta con el servicio.
test/unit/controllersSpec.js

describe('PhoneCat controllers', function() {


beforeEach(function(){
this.addMatchers({
toEqualData: function(expected) {
return angular.equals(this.actual, expected);
}
});
});
beforeEach(module('phonecatApp'));
beforeEach(module('phonecatServices'));

describe('PhoneListCtrl', function(){
var scope, ctrl, $httpBackend;
beforeEach(inject(function(_$httpBackend_, $rootScope, $controller) {
$httpBackend = _$httpBackend_;
$httpBackend.expectGET('phones/phones.json').
respond([{name: 'Nexus S'}, {name: 'Motorola DROID'}]);
scope = $rootScope.$new();
ctrl = $controller('PhoneListCtrl', {$scope: scope});
}));

it('should create "phones" model with 2 phones fetched from xhr', function() {
expect(scope.phones).toEqualData([]);
$httpBackend.flush();
expect(scope.phones).toEqualData(
[{name: 'Nexus S'}, {name: 'Motorola DROID'}]);
});

it('should set the default value of orderProp model', function() {


expect(scope.orderProp).toBe('age');
});
});

describe('PhoneDetailCtrl', function(){
var scope, $httpBackend, ctrl,
xyzPhoneData = function() {
return {
name: 'phone xyz',
images: ['image/url1.png', 'image/url2.png']
}
};

11 - REST y Servicios Personalizados

42

AngularJS

beforeEach(inject(function(_$httpBackend_, $rootScope, $routeParams, $controller) {


$httpBackend = _$httpBackend_;
$httpBackend.expectGET('phones/xyz.json').respond(xyzPhoneData());
$routeParams.phoneId = 'xyz';
scope = $rootScope.$new();
ctrl = $controller('PhoneDetailCtrl', {$scope: scope});
}));

it('should fetch phone detail', function() {


expect(scope.phone).toEqualData({});
$httpBackend.flush();
expect(scope.phone).toEqualData(xyzPhoneData());
});
});
});

En Karma, deberias ver el siguiente resultado.

Chrome 22.0: Executed 5 of 5 SUCCESS (0.038 secs / 0.01 secs)

11 - REST y Servicios Personalizados

43

AngularJS

Animaciones
En este ltimo captulo aplicaremos animaciones css y javascript al cdigo que creamos anteriormente.

Dependencias
La funcionabilidad de la animacion es provista por el mdulo ngAnimate de Angular, que viene separado del cdigo de
Angular original. Tambien aadiremos jQuery para las animaciones.
Utilizamos Bower para instalar las dependendencias. En este paso el archivo bower.json ser actualizado, incluyendo
nuestra nueva dependencia.

{
"name": "angular-seed",
"description": "A starter project for AngularJS",
"version": "0.0.0",
"homepage": "https://github.com/angular/angular-seed",
"license": "MIT",
"private": true,
"dependencies": {
"angular": "~1.3.0",
"angular-mocks": "~1.3.0",
"bootstrap": "~3.1.1",
"angular-route": "~1.3.0",
"angular-resource": "~1.3.0",
"jquery": "~2.1.1",
"angular-animate": "~1.3.0"
}
}

"angular-animate": "~1.3.0" le dice a Bower que instale el ultimo componente de angular-animate


"jquery": "2.1.1" le dice a Bower que instale jQuery en version 2.1.1
Template Los cambios necesarios para el codigo HTML estan en el archivo angular-animate.js por lo cual debemos
linkearlo con el mismo.
Esto es lo que necesitamos cambiar:
app/index.html

...
<!-- for CSS Transitions and/or Keyframe Animations -->
<link rel="stylesheet" href="css/animations.css">
...
<!-- jQuery is used for JavaScript animations (include this before angular.js) -->
<script src="bower_components/jquery/dist/jquery.js"></script>
...
<!-- required module to enable animation support in AngularJS -->
<script src="bower_components/angular-animate/angular-animate.js"></script>
<!-- for JavaScript Animations -->
<script src="js/animations.js"></script>
...

12 - Animaciones

44

AngularJS

Mdulo y Animaciones
app/js/animations.js

angular.module('phonecatAnimations', ['ngAnimate']);
// ...
// this module will later be used to define animations
// ...

Ahora, unamos nuestro nuevo mdulo al principal...


app/js/app.js

// ...
angular.module('phonecatApp', [
'ngRoute',
'phonecatAnimations',
'phonecatControllers',
'phonecatFilters',
'phonecatServices',
]);
// ...

Ya esta todo listo, ahora a crear animaciones!

Animando el ngRepeat con transiciones CSS


Comenzaremos aadiendo las transiciones CSS a nuestra directiva ngRepeat que aparece en la pagina phone-list.html.
Primero aadiremos clases CSS extra a nuestro elemento que se repite.
app/partials/phone-list.html

<!- Let's change the repeater HTML to include a new CSS class
which we will later use for animations:
-->
<ul class="phones">
<li ng-repeat="phone in phones | filter:query | orderBy:orderProp"
class="thumbnail phone-listing">
<a href="#/phones/{{phone.id}}" class="thumb"><img ng-src="{{phone.imageUrl}}"></a>
<a href="#/phones/{{phone.id}}">{{phone.name}}</a>
<p>{{phone.snippet}}</p>
</li>
</ul>

Notste que aadimos la clase CSS phone-listing? Esto es todo lo que necesitamos para que funcione.
Para nuestra transicion CSS:
app/css/animations.css

.phone-listing.ng-enter,
.phone-listing.ng-leave,
.phone-listing.ng-move {
-webkit-transition: 0.5s linear all;
-moz-transition: 0.5s linear all;

12 - Animaciones

45

AngularJS

-o-transition: 0.5s linear all;


transition: 0.5s linear all;
}
.phone-listing.ng-enter,
.phone-listing.ng-move {
opacity: 0;
height: 0;
overflow: hidden;
}
.phone-listing.ng-move.ng-move-active,
.phone-listing.ng-enter.ng-enter-active {
opacity: 1;
height: 120px;
}
.phone-listing.ng-leave {
opacity: 1;
overflow: hidden;
}
.phone-listing.ng-leave.ng-leave-active {
opacity: 0;
height: 0;
padding-top: 0;
padding-bottom: 0;
}

Como veras, la clase phone-listing esta combinada con la animacin al agregar o remover items de la lista:
ng-enter aplica cuando un nuevo elemento es aadido
ng-move aplica cuando se mueven items por la lista
ng-leave aplica cuando se borran items de la lista

12 - Animaciones

46

Você também pode gostar