AngularJS.pdf

December 28, 2017 | Author: gilberth | Category: Angular Js, Web Server, Html, Java Script, Web Application
Share Embed Donate


Short Description

Download AngularJS.pdf...

Description

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 - Más y más Templates 12. 9 - Filtros 13. 10 - Controladores de Eventos 14. 11 - REST y Servicios Personalizados 15. 12 - Animaciones

2

AngularJS

Qué es AngularJS? AngularJS es un framework JavaScript de desarrollo de aplicaciones web en el lado cliente, desarrollado por Google y utiliza el patrón MVC (Modelo-Vista-Controlador) A través de los siguientes capitulos aprenderás a construir una aplicación web con AngularJS.

Introduction

3

AngularJS

Tutorial Una muy buena forma de aprender Angular JS es trabajar a través de este tutorial, en el cual aprenderemos a construir una aplicación web. La app que construirás es un catálogo que muestra una lista de dispositivos del Android, te dejerá filtrar una lista para ver los teléfonos que te interesen, y luego podrás ver en detalle de cada dispositivo:

Sigue este tutorial para ver como angular hace a tu navegador más inteligente sin necesidad de utilizar extensiones nativas o plugins. Observa ejemplos sobre cómo utilizar data binding (enlace de datos) del lado del cliente para crear vistas dinámicas de datos que cambian de acuerdo a las acciones de tus usuarios. -Angular guarda las vistas en sincronización con tus datos sin necesidad de manipulación DOM. Aprende una mejor manera y más fácil de probar tus aplicaciones web, con Karma y Protractor. Aprende a utilizar inyección de dependencias y servicios para hacer una las tareas más comunes de la web, como es obtener datos de tu aplicación, mucho más fácil. Cuando termines este tutorial serás capaz de: Crear una aplicación dinámica 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 lógica de la aplicación del template y colocarla en los controladores. Obtener datos de un servidor mediante los servicios de Angular. Aplicar animaciones a tu aplicación, mediante ngAnimate. Conocer más recursos para aprender más sobre AngularJS. Este tutorial te guiará por todo el proceso de creación de una single page app y aprenderás a escribir y ejecutar pruebas unitarias end-to-end. Pruebas al final de cada paso y sugerencias para que aprendas más acerca de AngularJS y sobre la aplicación que vas a crear. Puedes ir a través de todo el tutorial en un par de horas o puedes profundizar más en cada paso. Si estás buscando una introducción más corta para AngularJS, revisa el documento Getting Started

Tutorial

4

AngularJS

Primeros Pasos El resto de esta página explica cómo puedes configurar el entorno de desarrollo . Si quieres puedes ir directamente a leer el resto del tutorial, Encendiendo motores.

Trabajando con el código 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, tendrás 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 revisión 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 versión 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 máquina, puedes descargar el manejo de dependencias o módulos corriendo el comando: npm install El archivo package.json lleva la configuración del paquete, donde se guardarán las dependencias de paquetes del proyecto y la configuración básica de este. Descarga las siguientes herramientas en el directorio node_modules Bower - Http-Server - Karma - Protractor - Corriendo npm install usará automáticamente bower para descargar el framework de Angular dentro del directorio app/bower_components El proyecto está preconfigurado con un algunos scripts npm que te harán más fácil realizar las tareas que necesitas hacer mientras desarrollas la app: npm start : inicia la aplicación 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

5

AngularJS

Los módulos Bower, Http-Server, Karma y Protractor son también 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 módulos globalmente utilizando: sudo npm install -g Por ejemplo, para instalar la línea de comandos de Bower ejectuta: sudo npm install -g bower omite la palabra sudo si estás en windows. A continuación, 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 mayoría de los exploradores modernos no permitien que JavaScript realice peticiones al servidor si la página se carga directamente desde el sistema de archivos. El proyecto Angular PhoneCat está configurado para alojar la aplicación en un servidor web estático durante la etapa de desarrollo, inicia el servidor ejecutando: npm start Esto creará un servidor Web en tu máquina local. que correrá en el puerto 8000. Ahora puedes ver tu aplicación en: http://localhost:8000/app/index.html

Corriendo pruebas unitarias Utilizamos pruebas unitarias para asegurarnos que el código JavaScript de nuestra aplicación 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 aplicación por separado. Con esto correrá la prueba unitaria mediante de Karma, un lanzador de tests. Lee la configuración del archivo en test/karma.conf.js Esta configuración dice: Abre tu navegador Chrome y conéctalo al Karma Ejecuta todas las pruebas unitarias en tu navegador Roporta los resultados de estas pruebas en la ventana de la línea 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 aplicación funcione correctamente como esperamos. están diseñadas para probar la aplicación del lado cliente enteramente, simulan la acción 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

6

AngularJS

update-webdriver (solo necesitas hacerlo una vez)

Protractor trabaja interactuando con la aplicación en ejecución, tenemos que iniciar nuestro servidor web: npm start Luego ejecuta en una nueva ventana esta linea de comandos: npm run protractor Protractor leerá la configuración del archivo en el test/protractor-conf.js. Esta configuración dice lo siguiente: Abre el navegador chrome y corre tu aplicación Ejecuta las pruebas de extremo a extremo en el navegador. Reporta los resultados de estas pruebas en la ventana de la línea 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 aplicación como un todo, se ejecuta correctamente.

Tutorial

7

AngularJS

Iniciando la aplicación En este paso, te familiarizaras con los archivos más importantes del código fuente, aprenderás cómo comunicarte con servidores de desarrollo como angular-seed que es básicamente un esqueleto de una típica aplicación AngularJs, y ejecutarás la aplicación 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 estás en el paso 0, step-1 si estás en el paso 1). Esto va a ser que cada cambio en tu directorio se pierda. Si no lo haz hecho aún es necesario instalar las dependencias ejecutando: NPM install Para ver como la aplicación se ejecuta en un navegador, dirigete a tu navegador y a la dirección: (http://localhost:8000/app/index.html) deberías ver la aplicación por defecto funcionando. Ahora puedes ver la página en tu navegador. Aún no es muy emocionante, pero está bien. La página HTML sólo muestra el "Aquí no hay nada aquí todavía!" pero el código contiene algunos de los elementos más importantes, que vamos a necesitar a medida que avancemos en el desarrollo de la aplicación: app / index.html:

Mi archivo HTML No hay nada aquí {{'todavía' + '!'}}

¿Qué está haciendo el código? La directiva ng-app:

0 - Iniciando la aplicacion

8

AngularJS

Ng-app es la directiva encargada de iniciar una aplicación Angular, indica el elemento raíz y se debe colocar como atributo en la etiqueta que quieres que sea la raíz de la aplicación. La directiva ng-app define nuestra aplicación.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 declaración 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

Este código descarga el script angular.js que registra una devolución de llamada que se ejecutará por el navegador cuando la página HTML que contiene este completamente descargada. Cuando la devolución de llamada se ejecuta, Angular busca la Directiva ngApp. Si la Directiva encuentra Angular, se inicia la aplicación con la raíz de la aplicación DOM siendo el elemento sobre el que se definió la Directiva ngApp. Expresión Doble llave {{}} Con estas expresiones también 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í {{'aún' + '!'}} Esta línea muestra las características 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: {{ expresión}} 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 expresión fue escrita. Las expresiones de AngularJS son mucho más que las expresiones de JavaScript: Estas pueden contener literales, operadores y variables. Por ejemplo:

Iniciando Aplicaciones Angular Puedes iniciar aplicaciones Angular de forma automática utilizando la directiva ngApp es muy fácil y es conveniente para la mayoría de los casos. En casos avanzados, como cuando se utilizan scripts, puedes utilizarlo de manera manual e imperativa. Cuando inicias la aplicación suceden 3 cosas importantes: Se crea el inyector que se utilizará para la inyección de dependencia. El inyector creará el root scope, que se convertirá en el contexto para el modelo de nuestra aplicación. Angular compilrá el DOM empezando por el elemento raíz ngApp, procesará las directivas y los bindings que se encuentran en el documento. Una vez que iniciaste la aplicación, se esperarán eventos en el navegador (como un clic, la pulsación de una tecla o una respuesta HTTP) que podrían 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 aplicación actualmente es muy sencilla. La plantilla contiene una directiva, un enlace estático, y nuestro modelo aún está vacío, pero pronto va a cambiar!

0 - Iniciando la aplicacion

9

AngularJS

¿Qué tipos de archivos hay en nuestro directorio de trabajo? La mayoría 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 (vía bower en los componentes de la aplicación/bower / carpeta) y las herramientas para el desarrollo de una aplicación web típica (via npm) A los efectos de este tutorial, modificaremos la plantilla angular-seed con los siguientes cambios: Remover la aplicación de ejemplo Agregar imagenes de telefonos a app/img/phones/ Agregar información de los telefonos (JSON) to app/phones/ *Agregar la dependencia en Bootstrap en el archivo bower.json

Experimentos Agrega esta nueva expresión al archivo index.html que resuelva esta operación matemática: 1 + 2 = {{ 1 + 2 }}

0 - Iniciando la aplicacion

10

AngularJS

Template Estatico Para ver cómo Angular mejora el estándar HTML, puedes crear una página HTML puramente estática y luego examinar cómo podemos convertir este código HTML en una plantilla que Angular use para mostrar dinámicamente el mismo resultado con cualquier conjunto de datos. En este paso agregaremos información básica sobre dos teléfonos celulares a una página HTML. Ahora, la página contiene una lista con información acerca de los dos teléfonos. 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 más importantes se enumeran a continuación. Y los puedes ver completos en GitHub app/index.html:

Nexus S Fast just got faster with Nexus S. Motorola XOOM™ with Wi-Fi The Next, Next Generation tablet.

Experimentos Intenta añadir más contenido estatico HTML al documento index.html. como por ejemplo: Total number of phones: 2

Resumen Agregamos a la aplicación 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 página web dinámica con AngularJS. Añadiremos una prueba que verifica el código para el controlador que vamos a añadir.Hay muchas formas de estructurar el código de una aplicación web. Para aplicaciones Angular, recomendamos el uso del patrón de diseño Modelo-Vista-Controlador (MVC) para desacoplar el código y evitar problemas futuros. La idea de la estructura MVC no es otra que presentar una organización en el código, donde el manejo de los datos (Modelo) estará separado de la lógica (Controlador) de la aplicación, y a su vez la información presentada al usuario (Vistas) se encontrará totalmente independiente. Es un proceso bastante sencillo donde el usuario interactúa con las vistas de la aplicación, éstas se comunican con los controladores notificando las acciones del usuario, los controladores realizan peticiones a los modelos y estos gestionan la solicitud según la información brindada. Esta estructura provee una organización esencial a la hora de desarrollar aplicaciones de gran escala, de lo contrario sería muy difícil 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 aplicación. Ejecutemos este comando en la terminal: git checkout -f step-2 Actualiza tu navegador o consulta en línea este paso: Step 2 Live Demo. Los cambios más importantes se enumeran a continuación. Los puedes ver completos en GitHub. En Angular, la vista es una proyección del modelo a través del documento HTML. Cuando el modelo cambia la vista refleja los cambios y viceversa. La vista se construye mediante Angular através de esta plantilla: app/index.html:

{{phone.name}} {{phone.snippet}}

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

12

AngularJS

serie de veces.. Esta repetición le dice a Angular que cree un elemento por cada telefono. Las expresiones cerradas entre llaves ( y) serán reemplazadas por el valor de cada expresión. 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 aplicación, 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 módulo de AngularJS, phonecatApp. Ten en cuenta que la directiva ng-app (en la etiqueta ) especifica el nombre del módulo phonecatApp como el módulo de carga cuando se inicia la aplicación. 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 , 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 función 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 información 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 más sobre Scopes puedes leer la documentación 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 podríamos 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 fácil que es crear una prueba unitaria en Angular. Puesto que la prueba es una parte crítica del desarrollo de software, hacemos que sea fácil 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 código JavaScript. cuando están escribiendo tests. Aunque Angular no requieren uso de Jasmine, escribiremos todas las pruebas de este tutorial en Jasmine. También puedes aprender acerca de Jasmine en la página 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 ejecución del comando: npm install Para ejecutar las pruebas y ver luego los cambios de archivos ejecuta: npm test Karma iniciará automáticamente una nueva instancia del navegador Chrome. Puedes ignorarlo y dejarlo funcionando en segundo plano. Karma utiliza el navegador para ejecutar la prueba. Deberías 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, sólo debes cambiar la fuente de archivos .js de prueba. Karma nota el cambio y vuelve a ejecutar las pruebas por usted.

Experimentos Añade esta linea de código: Total number of phones: {{phones.length}} 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: Hello, {{name}}! Actualiza el explorador y verifica que diga "Hola, mundo!". La actualización de la prueba unitaria

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

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

row number {{i}}

Ahora incrementa la lista de uno en uno:

row number {{i+1}}

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 aplicación, en el último paso. Ahora vamos a hacer algo mas simple; vamos a añadir una caja búsqueda de texto (sí, será sencillo!). También 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 búsqueda. Observa que la lista de teléfonos en la página cambia dependiendo de la búsqueda que realice el usuario.

Controlador No haremos cambios en el controlador.

Template app/index.html:

Search: {{phone.name}} {{phone.snippet}}

Hemos añadido una etiqueta y usado la función filtro para procesar la directiva de ngRepeat, en la etiqueta input. Esto le permite al usuario ingresar criterios de búsqueda y ver inmediatamente los efectos de su búsqueda en la lista de teléfonos. Este nuevo código se muestra a continuación: Data-Binding: Es una de las características más importantes de AngularJS. Cuando la página carga, Angular une el nombre que coloca el usuario en la caja de búsqueda a la variable del mismo nombre en el modelo de datos y los mantiene a los dos en sincronía. En este código, 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 función del filtro es utilizar el valor de la consulta para crear un nuevo array que contenga sólo los registros que coincidan con la query . ng-Repeat actualiza automáticamente la vista en respuesta a la cantidad de teléfonos devueltos por el filtro. El proceso es

totalmente transparente para el desarrollador.

Test En el segundo paso hemos aprendido a cómo escribir y ejecutar pruebas unitarias. Las pruebas unitarias son perfectas para probar controladores y otros componentes de nuestra aplicación escrita en JavaScript, pero no pueden fácilmente probar el cableado de nuestra aplicación o la manipulación del DOM. Para éstos fines, una prueba de extremo a extremo es una mucho mejor opción. La función de búsqueda fue totalmente implementada a través de vistas y binding, por lo que vamos a escribir nuestra primera prueba end-to-end, para comprobar que esta característica 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 están conectados correctamente. Observe lo fácil que es escribir pruebas end-to-end en Angular. Aunque este ejemplo es para una prueba simple, es realmente fácil 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: Google Phone Gallery: {{query}} . Sin embargo, al volver a cargar la página, 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 Asegúrese de remover la declaración 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 característica para permitir a los usuarios controlar el orden de los elementos de la lista de teléfonos. El orden dinámico 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. Además de la caja de búsqueda, la aplicación muestra un menú que permite a los usuarios controlen el orden en que aparecen los teléfonos. Ejecuta este comando: git checkout -f step-4

Template app/index.html:

Search: Sort by:

Alphabetical Newest {{phone.name}} {{phone.snippet}}

Realizamos los siguientes cambios a la plantilla index.html En primer lugar, hemos añadido en la etiqueta HTML 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 sección 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 automáticamente 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 teléfonos - y añadimos una propiedad edad a cada registro del teléfono. Esta propiedad se utiliza para clasificar los telefonos por edad. Hemos añadido una línea al controlador que establece el valor predeterminado de orderProp a la edad. Si no hubiéramos establecido este valor por defecto aquí, el filtro de orderBy no se inicializaria hasta que el usuario seleccione una opción 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 aplicación 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 dirección de nuestro modelo para la interfaz de usuario. Ahora si seleccionas "Orden alfabético" en el menú desplegable, también se actualizará el modelo y los teléfonos se reordenaran. El enlace de datos está haciendo su trabajo en la dirección 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 debería ver la siguiente salida en la pestaña 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 selección 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 instrucción que establece el valor de orderProp y verás que Angular temporalmente agregará una nueva opción 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 clasificación mediante la adición de un - símbolo antes del valor de: Oldest

4 - Enlace de Datos Bidireccional

23

AngularJS

XHRs e inyección de dependencias Ya no construiremos nuestra aplicación sólo con tres teléfonos. Vamos a buscar un conjunto de datos un poco más grande de nuestro servidor mediante el uso uno de los servicios integrados de Angular llamado $http . Vamos a utilizar inyección de dependencias de Angular (DI) para proporcionar el servicio al controlador PhoneListCtrl . Ahora hay una lista de 20 teléfonos, 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 más grande de teléfonos 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 petición HTTP al servidor web para recuperar los datos que están en el archivo app/phones/phones.json . Los servicios son gestionados por el subsistema de DI (Inyección de Dependencias) de Angular. La Inyección de dependencia ayuda a que tus aplicaciones web estén 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 petición 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 también puede haber sido generada dinámicamente por un servidor de back-end. El navegador y nuestra aplicación tienen el mismo aspecto. Por una cuestión de simplicidad utilizaremos un archivo json en este tutorial. El servicio de $http devuelve un objeto promise con el método success. Llamamos a este método para controlar la respuesta asincrónica y asignar los datos del teléfono al ámbito controlado por este controlador, como un modelo llamado teléfonos. 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 función constructora del controlador:

5 - XHRs e Inyeccion de Dependencias

24

AngularJS

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

$ Convención 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 convención de nomenclatura, los servicios integrados de Angular, el alcance métodos 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, también puedes notar que algunas propiedades comienzan con $$. Estas propiedades son consideradas privadas y no deberían 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 también se minificarían, y el inyector de dependencias no sería capaz de identificar los servicios correctamente. Podemos superar este problema de anotación de la función 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 escribiríamos:

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

Usa una anotación en linea donde, en vez de simplemente proveer una función vas a proveer un array. Este array contiene una lista de nombres de servicio seguidas por la función.

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

Ambos métodos funcionan si la función puede ser inyectada por Angular, está en vos usar el estilo que quieras en tus proyecto. Cuando estamos usando el segundo método, es común proveer el constructor en línea cómo una función anónima cuando creamos el controlador:

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

De aquí en adelante, vamos a usar el método 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 inyección de dependencias y nuestro controlador tiene dependencias, construir el controlador en nuestro test es un poco más complicado. Podríamos utilizar el operador new y proveer una implementación 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 métodos 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 teléfonos 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á: {{phones | filter:query | orderBy:orderProp | json}} 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 llevarán a ningun lado.

Datos Notaras que el archivo phones.json contiene un ID y una imagen para cada teléfono. 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.

{{phone.name}} {{phone.snippet}}

Testing Aplicamos una verificación 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 múltiples En este capitulo veremos como crear un diseño y añadir distintas vistas mediante el módulo ngRoute de AngularJS.

Dependencias La funcionabilidad del ruteo se provee via ngRoute, un módulo 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 diseño Nuestra aplicación de a poco se va convirtiendo más compleja. Durante los pasos anteriores, la app proveía a nuestros usarios una vista simple(la lista de todos nuestros teléfonos), y todo el código que estaba provisto en el archivo index.html. El proximo paso es crear una vista en la cual se muestre, en detalle, la información de cada dispositivo de nuestra lista. Para añadir una vista detallada, podriamos expandir el archivo index.html para que contenga el codigo de las dos vistas, pero se pondría complicado en pocos minutos. En cambio, vamos a transformar el index.html en una diseño que será común para todas las vistas de nuestra aplicación. Luego, agregaremos otras "diseños parciales" a nuestro diseño 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, diseños/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 módulo 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 módulos

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 diseño. 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/...

Search: Sort by: Alphabetical Newest

7 - Ruteo y Vistas Multiples

30

AngularJS

{{phone.name}} {{phone.snippet}}

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

TBD: detail view for {{phoneId}}

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

El módulo de app.js Para mejorar la organización de la aplicación vamos a utilizar el módulo de Angular, ngRoute y vamos a mover los controladores a nuestro propio módulo phonecatControllers(como verán 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 código. Necesitamos añadir los módulos como una dependencia a nuestra app. Listando estos dos módulos 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 módulos 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 diseño 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 diseño 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 módulo llamado phonecatControllers. Para aplicaciones pequeñas de Angular, es común crear solo un módulo para todos tus controladores, si son pocos. A medida que nuestra aplicación crezca, es mejor separar el código en diferentes módulos. Para aplicaciones más y más grandes, deberás crear módulos separados para mejorar el rendimiento de tu app. Debido a que nuestra app es pequeña, solo añadiremos 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

Más y más 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 petición 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 - Más y más Templates

33

AngularJS

{{phone.name}} {{phone.description}} Availability and Networks Availability {{availability}} ... Additional Features {{phone.additionalFeatures}}

Testing Ejecutaremos este código, el cual es parecido al que hicimos en el capítulo 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)

También añadimos un código End-To-End que navega por los detalles del Nexus S y verifica que el encabezado (heading) de la página es "Nexus S".

8 - Más y más 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 - Más y más Templates

35

AngularJS

Filtros En este capítulo, veremos como crear nuestro propio filtro para el template.

Filtros personalizados Para crear un nuevo filtro, crearermos un módulo phonecatFilters y registra tu filtro personalizado con este código: 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 módulo phonecatFiltrers como una dependencia de nuestro módulo 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 diseño para que funcione. app/index.html

... ...

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

...

9 - Filtros

36

AngularJS

Infrared {{phone.connectivity.infrared | checkmark}} GPS {{phone.connectivity.gps | checkmark}} ...

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 función beforeEach(module('phonecatFilters')) antes que cualquier ejecución de filtros. Esto carga el módulo phoneCatFilters dentro del injector para esta prueba. La función 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 añadir 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. También creamos el envento setImage que cambiará el valor de mainImageUrl.

Template app/partials/phone-detail.html

... ...

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, añadimos 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

... ... - 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 módulo API para registrar nuestros servicios personalizados usando un metodo de "fabricación". Utilizamos el nombre del servicio ('Phone') y la función 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 fácil el crear un cliente RESTful con tan solo pocas lineas de código. Esto puede despues ser usado por aplicaciones, en vez del servicio $http. app/js/app.js

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

Necesitamos añadir el módulo 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 fácil que usar $http para interactuar con los datos RESTful. También es más fácil enterender el cádigo 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 declaración que usaremos para todos los teléfonos de nuestra lista.

Testing 11 - REST y Servicios Personalizados

41

AngularJS

Ya que estamos utilzando el módulo 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 capítulo aplicaremos animaciones css y javascript al código que creamos anteriormente.

Dependencias La funcionabilidad de la animacion es provista por el módulo ngAnimate de Angular, que viene separado del código de Angular original. Tambien añadiremos 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

... ... ... ...

12 - Animaciones

44

AngularJS

Módulo y Animaciones app/js/animations.js

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

Ahora, unamos nuestro nuevo módulo 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 añadiendo las transiciones CSS a nuestra directiva ngRepeat que aparece en la pagina phone-list.html. Primero añadiremos clases CSS extra a nuestro elemento que se repite. app/partials/phone-list.html

View more...

Comments

Copyright ©2017 KUPDF Inc.
SUPPORT KUPDF