Está en la página 1de 153

Desarrollo de Apps con IONIC

Aprenda a crear aplicaciones para móviles desde cero con


el framework más poderoso y versátil

Victor Hugo Garcia


Este libro está a la venta en http://leanpub.com/desarrollodeappsconionic

Esta versión se publicó en 2017-02-07

This is a Leanpub book. Leanpub empowers authors and publishers with the Lean Publishing
process. Lean Publishing is the act of publishing an in-progress ebook using lightweight tools and
many iterations to get reader feedback, pivot until you have the right book and build traction once
you do.

© 2017 Victor Hugo Garcia


Para Laura, mi familia, amigos, y todos aquellos que me han enseñado algo
Índice general

Capítulo 1: ¿Qué es IONIC? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1


¿Qué significa eso de aplicaciones híbridas? . . . . . . . . . . . . . . . . . . . . . . . . . 2
Configurando el entorno de desarrollo . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Sobre el formato del libro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
Primera aplicación con ionic . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
Estructura de una aplicación en IONIC . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12

Capítulo 2: Primera Aplicación Completa . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31


Creación del Proyecto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
Creando una nueva página . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
Seteando la página raíz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
Creando un modelo de datos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
Listas y estructuras repetitivas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
Crear una ventana modal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51

Capítulo 3: Servidor de desarrollo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86


Api Rest . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
Servicios Web . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88

Capítulo 4: Búsquedas y filtros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123


Pipes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124
Gravatar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129

Capítulo 5: Corriendo la aplicación desde el emulador . . . . . . . . . . . . . . . . . . . . 135


APK . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140
Publicando una aplicación para Android . . . . . . . . . . . . . . . . . . . . . . . . . . . 141
Palabras de despedida . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144

Capítulo 6: Apéndices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145


Capítulo 1: ¿Qué es IONIC?
Si están leyendo este libro, probablemente ya sepan que INONIC es un framework para el desarrollo
de aplicaciones móviles híbridas, que permite desplegar dichas apps en dispositivos con Android,
IOs y Windows a partir de una única base de código.
Sin embargo, hay algunos conceptos que conviene tratar con mayor detalle para sacar luego mayor
provecho a esta herramienta.
Desde hace un tiempo, el desarrollo de aplicaciones ha estado limitado a un grupo de personas que
se especializan en entornos y lenguajes tales como Java, Objective C, Swift y otros.
Esta variedad, se desprende de la existencia de distintos sistemas operativos en multitud de
dispositivos, por lo que desarrollar una app para diferentes plataformas implica escribir código que
pueda ser compilado para el entorno destino, con el consecuente incremento en los esfuerzos de
desarrollo.
Ante esta situación, algunas personas pensaron que sería interesante poder escribir una única base
de código que pudiera luego ser compilado o traducido de alguna manera para obtener versiones de
la misma aplicación que funcionen en móviles, tabletas, y otros; prácticamente sin modificaciones.
Estos esfuerzos no son nuevos y en ese sentido IONIC no es el pionero. Sin embargo, existen ciertas
características que hacen de IONIC un framework único con el que desarrollar aplicaciones móviles
se convierte en una experiencia placentera y para nada traumática.
Estas son:

• IONIC permite utilizar las mismas tecnologías que se emplean para el desarrollo web (HTML5,
SASS, JavaScript, TypeScript, Angular) para la construcción de aplicaciones móviles. Esto
significa que se abre el juego y muchos desarrolladores web pueden pasar a participar del
mundo del desarrollo móvil, aplicando los conocimientos que ya tienen. Esto es algo fantástico
para todos aquellos que desean comenzar a desarrollar apps pronto, sin tener que meterse de
narices a aprender Java, Objective C, u otro lenguaje que puede resultar críptico.
• Existe una única base de código, que permite desplegar las aplicaciones construidas con IONIC
a las principales app store de la actualidad.
• Es un framework completamente gratuito y open source, con licencia MIT, lo que significa
que nunca se deberá pagar algún tipo de comisión por su uso.

Existen, por supuesto, otras características que iremos explorando a lo largo de este libro, pero las
arriba mencionadas son suficientes para percibir que se trata de una herramienta fantástica a la que
podemos sacarle mucho provecho.
Capítulo 1: ¿Qué es IONIC? 2

¿Qué significa eso de aplicaciones híbridas?


Al comienzo de este capítulo, se mencionó que IONIC es un framework para la construcción de
aplicación móviles híbridas. Veamos ahora qué significa eso y qué implicaciones tiene para nosotros.
Una aplicación híbrida, básicamente es una aplicación web que se ejecuta en un dispositivo móvil
dentro de un wrapper o “envoltorio”, que es el que en definitiva tiene acceso a la plataforma del
dispositivo. Es decir, podemos pensar en una app desarrollada con IONIC como un conjunto de
páginas web que son encapsuladas en un envoltorio, que es el que permite que este conjunto de
páginas se comporte como una aplicación nativa. El wrapper que utiliza IONIC es Cordova.
¿Esto significa que las apps construidas con IONIC son ejecutadas en un navegador web? Bueno, sí
y no. Las apps construidas con IONIC no se ejecutan a través de una aplicación de un navegador
móvil como Safari y Chrome, sino a través del navegador de bajo nivel del dispositivo (UIWebView
en IOS y WebView en Android) que a su vez tiene como wrapper a Cordova.
Esta es una consideración importante, ya que muchos de ustedes habrán notado, esta capa extra
tiene efectos sobre el rendimiento de las aplicaciones construidas con IONIC, e incluso algunos de
ustedes habrán oído críticas de que las aplicaciones híbridas son “lentas” y proporcionan una mala
experiencia al usuario.
Sobre este punto hay que señalar que en efecto, la capa extra necesaria para ejecutar las aplicaciones
híbridas provoca una sobrecarga que puede ir en detrimento de la performance. Es algo a tener en
cuenta sin dudas, pero esto por sí solo no determina que las aplicaciones híbridas sean lentas o que
el usuario tenga una mala experiencia con ellas.
Veremos cuáles son las prácticas a seguir para que la diferencia en performance entre nuestras
aplicaciones híbridas y las aplicaciones nativas, sea prácticamente inexistente.
En definitiva, IONIC es un framework maravilloso que nos permite introducirnos de lleno en el
mundo del desarrollo de aplicaciones para móviles, que se verán y comportarán estupendamente.
Vamos a por ello.

Configurando el entorno de desarrollo


En todo libro como éste, la configuración del entorno de desarrollo es una de esas partes que
desearíamos obviar, pero no podemos hacerlo.
Vamos a ver cómo instalar las herramientas necesarias para comenzar a construir aplicaciones de
inmediato.
Una cosa que debe tenerse en cuenta, sin embargo, es que existen lógicamente multitud de variantes
posibles en cuanto a sistemas operativos, plataformas, etc.; y por lo tanto no es posible contemplar
aquí todos los casos.
Lo bueno es que las herramientas que necesitamos no son demasiadas y para cada problema que
se pueda presentar con su instalación la solución está a una búsqueda en Google de distancia.
Comencemos pues.
Capítulo 1: ¿Qué es IONIC? 3

Navegador web
Aunque en principio podría utilizarse cualquiera, recomiendo utilizar Google Chrome. Créanme, me
lo van a agradecer cuando echemos mano a sus herramientas de depuración.
Editor de texto
Aunque en principio podríamos trabajar con cualquier editor de texto, es conveniente contar con
algún editor de texto inteligente, o incluso aún mejor una IDE para que el trabajo sea más provechoso.
Particularmente, a mí me gusta mucho Sublime Text¹. Es un muy buen editor, con funcionalidades
tales como el resaltado de texto, y multitud de plugins disponibles para agregar aún más poder si
hiciera falta. Si trabajan con la versión gratuita, de vez en cuando tendrán que lidiar con mensajes que
les sugieren comprar el producto, pero esta molestia se ve compensada con creces por la versatilidad
del producto.
Otra herramienta muy buena es Visual Studio Code², un editor de código muy potente que incluye:
opciones de depuración, integración con Git, entre muchas otras.
Cualquiera de estas dos opciones servirán muy bien a nuestros propósitos.
Apache y MySQL
Si bien nos ocuparemos del desarrollo móvil, lógicamente nuestras aplicaciones consumirán datos.
Estos datos habitualmente provendrán de algún servicio web.
Para que podamos ver cómo se efectúan las peticiones a servicios web, construiremos los nuestros.
No se preocupen, no nos enfrascaremos en demasiados detalles. Lo importante es que tengamos una
fuente a partir de la cual podamos consumir datos.
Una manera sencilla de configurar Apache y MySQL sin liarnos demasiado, es utilizar por ejemplo
Xampp, una aplicación disponible para las principales plataformas y que nos permitirá tener todo
funcionando de manera sencilla.
En los apéndices correspondientes, se muestra como instalar Xampp en Windows y Linux.
Shell
Este paso no es estrictamente necesario, pero lo incluyo porque puede ser de utilidad. Si están
desarrollando con Windows, puede ser útil contar con una terminal de comandos poderosa que
puede aumentar nuestra productividad.
En dicho caso, recomiendo Cygwin, para darle un sabor Linux al entorno Windows.
Android SKD
Para poder desplegar nuestras apps en Android (lo que implica generar el apk, firmarla y alinearla
antes de poder publicarla), debemos contar con el Android SDK. Para nuestros propósitos, no es
necesario instalar todo el Android Studio (el IDE de desarrollo) y podemos simplemente instalar el
SKD. Podemos encontrar ambas opciones aquí³.
¹https://www.sublimetext.com/3
²https://code.visualstudio.com/
³https://developer.android.com/studio/index.html
Capítulo 1: ¿Qué es IONIC? 4

Node JS
Para crear proyectos en IONIC, necesita contar con una versión actualizada de CLI (command line
interface) y Cordova, para lo cual previamente deberán tener instalado Node.js. Es conveniente que
instalen la última versión, la cual pueden encontrar aquí⁴.
Para comprobar qué versión de node tienen instalada, pueden ejecutar el comando node –v desde
la línea de comandos:

Versión de NodeJS

Esas son todas las herramientas que necesitamos para trabajar. Todas ellas vienen en la forma de un
sencillo instalador, por lo que no deberíamos tener problemas para ponerlas en marcha. He incluido
apéndices para aquellos casos que pudieran resultar más complicados. En caso de que encuentren
alguna dificultad, no desesperen. Configurar el entorno de desarrollo es la parte más tediosa de todo
el proceso.
Lo bueno es que las dificultades con las que puedan encontrarse, seguramente ya se le han presentado
a alguien más, y una búsqueda en Google puede resultar de suma utilidad. También existen sitios
como Stackoverflow donde mucha gente está dispuesta a echar una mano. Recuerden siempre ser
concisos y amables en sus preguntas, y les irá bien. Asimismo, si alguna respuesta les ha sido
de utilidad, no olviden agradecer y reconocer la respuesta como válida. La persona que les haya
respondido lo valorará mucho y estará más dispuesta a echar una mano en la próxima oportunidad.

Sobre el formato del libro


En este libro encontrarán todo el código necesario para construir las aplicaciones que desarrolla-
remos. El código está copiado directamente desde mi IDE para asegurarme de que es totalmente
funcional. Sin embargo y como podrán notar, debido a las restricciones de formato, puede que no
resulte muy legible tal y como aparece aquí. Puede utilizar algún formateador de código en línea
para embellecerlo.
⁴https://nodejs.org/es/
Capítulo 1: ¿Qué es IONIC? 5

En cualquier caso, para cada fragmento de código relevante que aparece en libro, incluyo el
correspondiente Gist. Para los que no están al tanto, un Gist es un fragmento de código compartido
a través de la plataforma github. Dicho código está correctamente formateado y resaltado, por lo
que será mucho más agradable de ver.
Esto es todo como introducción. Gracias por su paciencia, y ahora sí vamos a ensuciarnos las manos.

Primera aplicación con ionic


Una vez que tenemos todo el entorno de desarrollo configurado, debemos comenzar por instalar la
CLI de ionic y cordova. Para ello empleamos el comando:
npm install -g ionic cordova
Nótese que estamos empleando la bandera –g. Esto indica que deseamos instalar la utilidad de forma
global, con lo que automáticamente estará disponible para todos nuestros proyectos.
Hecho esto, podemos dirigirnos al directorio en el que deseamos crear nuestra aplicación. Por
ejemplo, podemos tener un directorio MisApps, dentro del cual pondremos todas las aplicaciones de
prueba que desarrollemos.
Ubicados dentro de este directorio, ejecutamos el comando:
ionic start ionicTestOne –v2
Con esto, habremos creado una aplicación denominada ionicTestOne. El modificador –v2 se utiliza
para indicar que deseamos trabajar con la versión 2 de ionic.

Primer Proyecto

Una vez que el proceso ha finalizado, ingresamos al directorio recientemente creado, y desde allí
ejecutamos:
ionic serve
Capítulo 1: ¿Qué es IONIC? 6

Lo que estamos haciendo con esto, es ejecutar el servidor embebido que tiene ionic para probar
nuestra aplicación en el navegador. Para detener el servidor debemos presionar la letra q.
NOTA: puede ser que al momento de ejecutar ionic serve obtengamos una salida similar a esta en
el navegador:

Error el ejecutar ionic serve

¿Por qué ocurre esto? Para comprenderlo abramos nuestro proyecto en nuestro editor de texto. La
estructura es la siguiente:

Estructura del proyecto

En la raíz, vemos un archivo denominado package.json, que define cosas tales como los scripts a
ejecutar cuando lanzamos un comando, dependencias, etc.
El contenido de este paquete variará de acuerdo a la forma en que nuestra aplicación fue creada
originalmente.
Ionic ofrece un número de plantillas que sirven como punto de partida para nuestros proyectos.
Cuando nosotros ejecutamos al comienzo ionic start ionicTestOne –v2, se creó un proyecto con una
plantilla por defecto que nos proporciona una aplicación con 3 tabs o pestañas.
Al momento de escribir este libro, el contenido del archivo package.json es el siguiente:
Capítulo 1: ¿Qué es IONIC? 7

1 {
2 "name": "ionic-hello-world",
3 "author": "Ionic Framework",
4 "homepage": "http://ionicframework.com/",
5 "private": true,
6 "scripts": {
7 "clean": "ionic-app-scripts clean",
8 "build": "ionic-app-scripts build",
9 "ionic:build": "ionic-app-scripts build",
10 "ionic:serve": "ionic-app-scripts serve"
11 },
12 "dependencies": {
13 "@angular/common": "2.2.1",
14 "@angular/compiler": "2.2.1",
15 "@angular/compiler-cli": "2.2.1",
16 "@angular/core": "2.2.1",
17 "@angular/forms": "2.2.1",
18 "@angular/http": "2.2.1",
19 "@angular/platform-browser": "2.2.1",
20 "@angular/platform-browser-dynamic": "2.2.1",
21 "@angular/platform-server": "2.2.1",
22 "@ionic/storage": "1.1.7",
23 "ionic-angular": "2.0.0-rc.4",
24 "ionic-native": "2.2.11",
25 "ionicons": "3.0.0",
26 "rxjs": "5.0.0-beta.12",
27 "zone.js": "0.6.26"
28 },
29 "devDependencies": {
30 "@ionic/app-scripts": "0.0.47",
31 "typescript": "2.0.9"
32 },
33 "description": "ionicTestOne: An Ionic project",
34 "cordovaPlugins": [
35 "cordova-plugin-device",
36 "cordova-plugin-console",
37 "cordova-plugin-whitelist",
38 "cordova-plugin-splashscreen",
39 "cordova-plugin-statusbar",
40 "ionic-plugin-keyboard"
41 ],
42 "cordovaPlatforms": []
Capítulo 1: ¿Qué es IONIC? 8

43 }

El problema se encuentra en la sección “scripts”. Debemos reemplazar

1 "scripts": {
2 "clean": "ionic-app-scripts clean",
3 "build": "ionic-app-scripts build",
4 "ionic:build": "ionic-app-scripts build",
5 "ionic:serve": "ionic-app-scripts serve"
6 },

Por
Del libro:

1 "scripts": {
2 "build": "ionic-app-scripts build",
3 "watch": "ionic-app-scripts watch",
4 "serve:before": "watch",
5 "emulate:before": "build",
6 "deploy:before": "build",
7 "build:before": "build",
8 "run:before": "build"
9 },

Gist⁵
Por ahora no se preocupen por la razón y simplemente hagan el cambio.
Ahora, si ejecutan ionic serve, podrán finalmente ver la aplicación funcionando en el navegador:
⁵https://gist.github.com/vihugarcia/3cbcd246475b2ba03289b7403ed02ecd
Capítulo 1: ¿Qué es IONIC? 9

Primera Aplicación

Vemos que se trata de una muy sencilla aplicación con tres pestañas visibles en la parte inferior.
La apariencia que muestra se debe a que estamos ejecutándola en el navegador. Sin embargo, si
estamos utilizando Chrome podemos utilizar las herramientas que nos proporciona para obtener
una vista similar a la de un móvil.

Consola de desarrollador
Capítulo 1: ¿Qué es IONIC? 10

Desde esta vista, tenemos disponibles herramientas de depuración que nos serán extremadamente
útiles.
Analicemos brevemente algunas de las pestañas de la consola de desarrollador.
En la pestaña Elements podemos visualizar los elementos del DOM (Document Object Model) que
conforman la página que estamos visualizando en ese momento.
En la porción inferior, podemos observar además que tenemos acceso a los estilos que están aplicados
a los elementos que tenemos seleccionados.

Pestaña Elements

Por ejemplo, si seleccionamos en la pestaña el tag html ion-app, podemos ver como en la parte
inferior se muestran los estilos aplicados a esa sección del documento.
La pestaña Console será probablemente la que más visitemos. Allí podremos ver los errores y/o
Capítulo 1: ¿Qué es IONIC? 11

advertencias relacionadas a los scripts que se están ejecutando. Será nuestra principal fuente de
información para solucionar errores.
Si la seleccionamos en este momento, veremos que nos muestra dos advertencias.

Pestaña Console

Estas advertencias simplemente se producen porque estamos corriendo la aplicación en un navega-


dor en lugar de un dispositivo o simulador, por lo que no tenemos acceso a ciertas propiedades y
funciones nativas. No es algo que deba preocuparnos por ahora.
En la pestaña Network se muestran todas las peticiones que se realizan a recursos tales como
imágenes, datos de servicios web y otros. También la veremos frecuentemente ya que toda aplicación
tiene la necesidad de consumir datos. La comunicación es en ambos sentidos. Por un lado por
ejemplo podemos obtener una serie de posts a través de un servicio web, y por otro podemos enviar
comentarios sobre dicho post.

Pestaña Network

Por ahora es suficiente. Ahora comencemos a analizar la estructura de nuestra aplicación.


Capítulo 1: ¿Qué es IONIC? 12

Estructura de una aplicación en IONIC


Nuestro trabajo, lo haremos principalmente en el directorio src. Este contiene el código que da vida
a nuestra aplicación.

Directorio src

Dentro de este directorio, encontramos los siguientes:


App: en este directorio encontramos los archivos necesarios para inicializar nuestra aplicación.
Iremos viendo cada uno de ellos a medida que avancemos.
Assets: aquí se guardan recursos estáticos tales como imágenes.
Pages: en este directorio se encuentran cada una de las páginas que componen nuestra aplicación.
Recordemos que una aplicación de IONIC está compuesta por un conjunto de páginas. Cada una de
estas páginas vive en su propio directorio, lo cual contribuye a mantener la estructura ordenada y
manejable.
Theme: en este directorio, se encuentra por defecto un único archivo denominado variables.scss.
Vale la pena analizarlo con mayor detenimiento.
¿Qué es un archivo scss?
Seguramente, en sus proyectos habrán trabajado con hojas de estilo, archivos con extensión css que
Capítulo 1: ¿Qué es IONIC? 13

permiten definir la apariencia de secciones de una página o incluso del sitio completo. Por ejemplo,
una entrada típica de css puede lucir así:

1 #header {
2 background-color: #387ef5;
3 }

Ahora bien, ¿qué ocurre si el color #387ef5 es usado en distintos lugares de nuestra aplicación?
Entonces tenemos que repetirlo tantas veces como sea necesario. ¿Y qué ocurre si en el futuro
deseamos reemplazarlo por otro? Entonces deberemos buscar cada ocurrencia en nuestro proyecto
y reemplazarlo.
Una solución sería poder guardar dicho valor en una variable, algo así como:

1 $primary = #387ef5;

Lamentablemente, css no permite esto.


La solución es emplear una extensión de css denominada Sass (Syntactically Awesome Style Sheet),
a la que podemos definir como un lenguaje de hojas de estilo.
Lo que debemos saber, es que un archivo sass (identificado por su extensión scss), es traducido a css.
Sass nos proporciona una serie de ventajas sobre css que lo convierten en una herramienta
extremadamente útil. Entre ellas, se encuentra la posibilidad de poder definir variables. Por ejemplo,
si observamos el archivo variables.scss, podemos ver la siguiente sección:

1 $colors: (
2 primary: #387ef5,
3 secondary: #32db64,
4 danger: #f53d3d,
5 light: #f4f4f4,
6 dark: #222
7 );

Lo que se está haciendo aquí, es definir un mapa de colores denominado $colors, dentro del cual
se encuentran una serie de colores definidos por nombre de manera que podamos reutilizarlos a lo
largo de nuestra aplicación.
Podemos cambiar los códigos de color, y agregar o remover colores. El único color que es obligatorio
definir en el mapa $colors es primary.
Pronto veremos todo esto en acción.
Ahora vamos a centrarnos en las páginas que componen nuestra aplicación. Si observamos el
directorio pages, veremos que dentro de él se encuentran 4 directorios, que se corresponden a las
Capítulo 1: ¿Qué es IONIC? 14

páginas de nuestra aplicación: home, about y contact; el otro directorio, tabs, contiene la página que
se encarga de mostrar las 3 pestañas anteriores.
Veamos dentro del directorio home. Podemos observar que contiene 3 archivos: home.html,
home.scss y home.ts.
El archivo home.html, es el que contiene el código html que conforma la página. Si lo abrimos,
podemos ver que el código es el siguiente:

1 <ion-header>
2 <ion-navbar>
3 <ion-title>Home</ion-title>
4 </ion-navbar>
5 </ion-header>
6
7 <ion-content padding>
8 <h2>Welcome to Ionic!</h2>
9 <p>
10 This starter project comes with simple tabs-based layout for apps
11 that are going to primarily use a Tabbed UI.
12 </p>
13 <p>
14 Take a look at the <code>src/pages/</code> directory to add or change tabs,
15 update any existing page or create new pages.
16 </p>
17 </ion-content>

En él podemos reconocer etiquetas html ordinarias, tales como h2 y p, y otras que son específicas
de ionic y que comienzan con el prefijo ion.
Por ejemplo, en la parte superior tenemos:

1 <ion-header>
2 <ion-navbar>
3 <ion-title>Home</ion-title>
4 </ion-navbar>
5 </ion-header>

Se define un encabezado, dentro de él una barra de navegación (aquí pondríamos típicamente


botones o un menú), y dentro de la barra de navegación un título.
Las etiquetas que comienzan con el prefijo ion nos permiten definir elementos que son específicos
de ionic, pero lo bueno es que son semánticamente compatibles con html5. Anteriormente vimos
como en el archivo variables.scss se definen variables globales que determinan la presentación de
Capítulo 1: ¿Qué es IONIC? 15

nuestra aplicación en su conjunto. Veamos cómo usarlas. Reemplacemos la sección anterior por la
siguiente:
Del libro:

1 <ion-header>
2 <ion-navbar color="primary">
3 <ion-title>Home</ion-title>
4 </ion-navbar>
5 </ion-header>

Gist⁶
Podemos ver ahora cómo ha cambiado la apariencia de la aplicación

Barra de Navegación Home

Lo que hemos hecho es agregar la propiedad color=”primary” para la etiqueta navbar. Realicemos
el mismo cambio en las páginas about.html y contact.html.
La barra de navegación de todas las páginas mostrará ahora el mismo color.
Vamos a aprovechar lo que hemos visto hasta ahora para personalizar un poco el estilo de nuestra
aplicación.
Regresamos al archivo variables.scss dentro del directorio theme y reemplacemos el mapa $colors
por lo siguiente:
Del libro:

⁶https://gist.github.com/vihugarcia/650c8411e8103da98b2ec0fdfaff707f
Capítulo 1: ¿Qué es IONIC? 16

1 $colors: (
2 primary: #D93240,
3 secondary: #638CA6,
4 danger: #F19143,
5 light: #9E99A7,
6 dark: #313628,
7 favorite: #7FB069
8 );

Gist⁷
Podemos ver ahora que la barra de navegación de todas las páginas presenta un lindo color
personalizado.

Nuevo Color de la Barra

Es importante que el estilo de nuestra aplicación se adapte al tipo de producto que estamos
desarrollando y que éste difiera del diseño genérico que proporciona IONIC. Una buena imagen
es de fundamental importancia para el éxito de nuestra aplicación. Sigamos modificando la página
home. De regreso al archivo home.html Reemplacemos el contenido, delimitado por las etiquetas
ion-content, por lo siguiente:
Del libro:

1 <ion-content padding>
2 <h2>¡Hola Ionic!</h2>
3 <p>
4 Esta es la página inicial de nuestro proyecto.
5 </p>
6 </ion-content>

⁷https://gist.github.com/vihugarcia/c509daff7fa7224d85632ad011e869e0
Capítulo 1: ¿Qué es IONIC? 17

Gist⁸
Si estamos ejecutando el servidor embebido mediante ionic serve, habremos notado que los cambios
se reflejan automáticamente. Nuestra página de inicio ahora se verá así:

Nueva Página de Inicio

Fantástico. Vamos progresando. Ahora centremos nuestra atención en el archivo home.ts. Los
archivos con extensión ts, son archivos typescript. Typescript es un lenguaje de scripting basado
en el estándar ECMA6. El estándar ECMA es el mismo en el que se basa el ultra popular lenguaje
Javascript, pero a diferencia de este, Typescript agrega nuevas características tales como tipado y
verdaderas clases, cosas que en Javascript sólo pueden simularse.
Prácticamente toda página en un proyecto de IONIC tiene asociado un archivo ts, que posteriormente
será traducido a un archivo js.
El contenido del archivo home.ts es el siguiente:

1 import { Component } from '@angular/core';


2
3 import { NavController } from 'ionic-angular';
4
5 @Component({
6 selector: 'page-home',
7 templateUrl: 'home.html'
8 })
9 export class HomePage {
10
11 constructor(public navCtrl: NavController) {
12
13 }
14
15 }

Las primeras dos líneas, importan clases necesarias para el funcionamiento de nuestra página.
⁸https://gist.github.com/vihugarcia/3684f20d971ba3ea8f0d63d5d39d279d
Capítulo 1: ¿Qué es IONIC? 18

Es importante aquí entender que IONIC es un framework orientado a componentes, y por lo tanto,
cada elemento que conforma nuestra aplicación (desde una página hasta una sección de la misma)
es un componente. De hecho, determinar qué componentes conformarán nuestra aplicación es una
de las tareas fundamentales del diseño.
Con esto en mente, analicemos las líneas siguientes:

1 @Component({
2 selector: 'page-home',
3 templateUrl: 'home.html'
4 })

Con esto estamos declarando un componente, en este caso una página. Vemos que la declaración
tiene un par de propiedades.
selector, hace referencia la etiqueta html que identificará al componente. En este caso, estamos
diciendo que cuando en nuestro código html se encuentre un par de etiquetas <page-home></page-
home>, dichas etiquetas contendrán el código html de nuestro componente.
templateUrl contiene el nombre del archivo donde está definida la estructura de nuestro componente.
Se trata del archivo home.html que ya hemos analizado y modificado.
Alternativamente, el código html se podría definir sin recurrir a un archivo html, utilizando la
propiedad template y poniendo el código html entre comillas simples o dobles. Esto, sin embargo,
no es recomendable a menos que se trate de un contenido super sencillo.
Veamos ahora las últimas líneas:

1 export class HomePage {


2
3 constructor(public navCtrl: NavController) {
4
5 }
6
7 }

Lo que hacemos aquí es exportar la clase que contiene nuestro componente, asignándole en este
caso el nombre HomePage. Esto permitirá que podamos usar el componente desde cualquier otro
componente donde sea necesario, utilizando previamente una sentencia import. Veremos esto
cuando analicemos el componente Tabs.
Los componentes que corresponden a las páginas about y contact son prácticamente iguales.
Tenemos las sentencias import, la definición del componente, y luego la exportación de la clase
para que el componente pueda ser utilizado.
Analicemos ahora el componente Tabs.
Capítulo 1: ¿Qué es IONIC? 19

Cuando creamos el proyecto, IONIC construyó por defecto una aplicación que tiene un layout
gobernado por pestañas, es decir, la navegación entre páginas se realiza mediante pestañas. Esto
quiere decir que además de los componentes correspondientes a cada una de las páginas que se
muestran en pestañas, necesitamos un componente que contenga la navegación. Dicho componente
es Tabs.
Examinemos el archivo tabs.ts. Su contenido es el siguiente:

1 import { Component } from '@angular/core';


2
3 import { HomePage } from '../home/home';
4 import { AboutPage } from '../about/about';
5 import { ContactPage } from '../contact/contact';
6
7 @Component({
8 templateUrl: 'tabs.html'
9 })
10 export class TabsPage {
11 // this tells the tabs component which Pages
12 // should be each tab's root Page
13 tab1Root: any = HomePage;
14 tab2Root: any = AboutPage;
15 tab3Root: any = ContactPage;
16
17 constructor() {
18
19 }
20 }

Es ligeramente más complejo que lo visto hasta ahora.


La primera línea, es un import de los componentes del núcleo de angular, necesario para poder
trabajar con componentes:

1 import { Component } from '@angular/core';

Algo que podamos notar, es que a diferencia de las páginas vistas anteriormente, no se encuentra
un import del componente NavController.
Esto es así porque en esta página no se hace uso de una barra de navegación.
Las tres líneas siguientes son imports de las páginas home, about y contact, que son las que pueden
visualizarse en las pestañas.
Capítulo 1: ¿Qué es IONIC? 20

1 import { HomePage } from '../home/home';


2 import { AboutPage } from '../about/about';
3 import { ContactPage } from '../contact/contact';

Si estos imports no estuvieran, no podríamos usar los componentes asociados a cada una de dichas
páginas.
Luego viene la declaración del componente:

1 @Component({
2 templateUrl: 'tabs.html'
3 })

En este caso, a diferencia de los casos anteriores, no estamos definiendo un selector, es decir una
etiqueta html asociada al componente. Esto es así porque se trata del componente raíz, a partir del
cual se agregarán todos los siguientes.
A continuación, como siempre, se debe exportar la clase para que pueda ser utilizada:

1 export class TabsPage {


2 // this tells the tabs component which Pages
3 // should be each tab's root Page
4 tab1Root: any = HomePage;
5 tab2Root: any = AboutPage;
6 tab3Root: any = ContactPage;
7
8 constructor() {
9
10 }
11 }

Obsérvese que se definen tres variables, y a cada una de ellas se le asigna cada una de las clases que
hacen referencia a las páginas que se mostrarán en las pestañas.
Nótese también que después de cada variable, se tienen dos puntos y una declaración de tipo antes
de la asignación.
Como habíamos mencionado anteriormente, typescript es un lenguaje tipado, es decir, permite
declarar los tipos asociados a cada variable de la forma nombreVariable: tipo. Esta declaración del
tipo puede estar seguida o no de una asignación.
También podemos observar que al igual que en las clases vistas anteriormente, se tiene un método
constructor sin implementar.
Capítulo 1: ¿Qué es IONIC? 21

Típicamente allí realizaríamos todas las tareas de inicialización requeridas. Por ejemplo, dentro del
constructor podríamos llamar a un método encargado de iniciar una petición a un servicio web para
obtener datos necesarios.
El archivo tabs.html es muy simple.

1 <ion-tabs>
2 <ion-tab [root]="tab1Root" tabTitle="Home" tabIcon="home"></ion-tab>
3 <ion-tab [root]="tab2Root" tabTitle="About" tabIcon="information-circle">
4 </ion-tab>
5 <ion-tab [root]="tab3Root" tabTitle="Contact" tabIcon="contacts"></ion-tab>
6 </ion-tabs>

Tenemos un componente tabs donde se definen cada una de las pestañas que estarán disponibles.
Vayamos ahora un poco más lejos y veamos cómo podemos agregar una nueva página y mostrarla
en su correspondiente pestaña.
Una de las mejores características de IONIC es su CLI (Command Line Interface), que nos permite
utilizar una serie de comandos que simplifican enormemente el trabajo. Siempre desde la raíz de
nuestro proyecto, ejecutemos el comando:
ionic g page privacy
Si todo ha ido bien, deberíamos ver un nuevo directorio denominado privacy, dentro del directorio
pages.

Página privacy

Estupendo.
Podemos observar que el directorio privacy contiene los tres mismo tipos de archivo con los que ya
estamos familiarizados.
El contenido del archivo privacy.html es el siguiente:
Capítulo 1: ¿Qué es IONIC? 22

1 <!--
2 Generated template for the Privacy page.
3
4 See http://ionicframework.com/docs/v2/components/#navigation for more info on
5 Ionic pages and navigation.
6 -->
7 <ion-header>
8
9 <ion-navbar>
10 <ion-title>privacy</ion-title>
11 </ion-navbar>
12
13 </ion-header>
14
15
16 <ion-content padding>
17
18 </ion-content>

Tenemos un encabezado, dentro de él una barra de navegación, y dentro de esta última un título.
En primer lugar, asignemos a la barra de navegación el mismo color que el de las otras páginas.
La etiqueta debería quedarnos como:
<ion-navbar color=”primary”>
Luego, reemplacemos el título por el siguiente: Política de Privacidad
Podemos ver que el contenido de la página está vacío:

1 <ion-content padding>
2
3 </ion-content>

En primer lugar, agreguemos un título utilizando la etiqueta <h2>


Luego, agreguemos un texto de ejemplo que podríamos utilizar para señalar la política de privacidad
de nuestra aplicación.
El texto fue generado a partir de una plantilla en https://politicadeprivacidadplantilla.com/⁹
Es un recurso que puede serles de utilidad para un proyecto web o móvil.
En definitiva, el contenido debería quedarnos de la siguiente manera:
Del libro:
⁹https://politicadeprivacidadplantilla.com/
Capítulo 1: ¿Qué es IONIC? 23

1 <ion-content padding>
2 <h2>Política de Privacidad</h2>
3
4 <p>El presente Política de Privacidad establece los términos en que TestOne
5 usa y protege la información que es proporcionada por sus usuarios al momento
6 de utilizar su sitio web. Esta compañía está comprometida con la seguridad de
7 los datos de sus usuarios. Cuando le pedimos llenar los campos de información
8 personal con la cual usted pueda ser identificado, lo hacemos asegurando
9 que sólo se empleará de acuerdo con los términos de este documento. Sin
10 embargo esta Política de Privacidad puede cambiar con el tiempo o ser
11 actualizada por lo que le recomendamos y enfatizamos revisar continuamente
12 esta página para asegurarse que está de acuerdo con dichos cambios.</p>
13
14 <h3>Información que es recogida</h3>
15
16 <p>Nuestro sitio web podrá recoger información personal por ejemplo: Nombre,
17 información de contacto como su dirección de correo electrónica e información
18 demográfica. Así mismo cuando sea necesario podrá ser requerida información
19 específica para procesar algún pedido o realizar una entrega o facturación.</p>
20
21 <h3>Uso de la información recogida</h3>
22
23 <p>Nuestro sitio web emplea la información con el fin de proporcionar el
24 mejor servicio posible, particularmente para mantener un registro de usuarios,
25 de pedidos en caso que aplique, y mejorar nuestros productos y servicios.
26 Es posible que sean enviados correos electrónicos periódicamente a
27 través de nuestro sitio con ofertas especiales, nuevos productos y otra
28 información publicitaria que consideremos relevante para usted o que
29 pueda brindarle algún beneficio, estos correos electrónicos serán enviados
30 a la dirección que usted proporcione y podrán ser cancelados en
31 cualquier momento.</p>
32
33 <p>TestOne está altamente comprometido para cumplir con el compromiso de
34 mantener su información segura. Usamos los sistemas más avanzados y los
35 actualizamos constantemente para asegurarnos que no exista ningún
36 acceso no autorizado.</p>
37
38 <h3>Enlaces a Terceros</h3>
39
40 <p>Este sitio web pudiera contener enlaces a otros sitios que pudieran ser de
41 su interés. Una vez que usted dé clic en estos enlaces y abandone nuestra
42 página, ya no tenemos control sobre al sitio al que es redirigido y por lo tanto
Capítulo 1: ¿Qué es IONIC? 24

43 no somos responsables de los términos o privacidad ni de la protección de sus


44 datos en esos otros sitios terceros. Dichos sitios están sujetos a sus propias
45 políticas de privacidad por lo cual es recomendable que los consulte para
46 confirmar que usted está de acuerdo con estas.</p>
47
48 <h3>Control de su información personal</h3>
49
50 <p>En cualquier momento usted puede restringir la recopilación o el uso de
51 la información personal que es proporcionada a nuestro sitio web. Cada vez que
52 se le solicite rellenar un formulario, como el de alta de usuario, puede
53 marcar o desmarcar la opción de recibir información por correo electrónico.
54 En caso de que haya marcado la opción de recibir nuestro boletín o publicidad
55 usted puede cancelarla en cualquier momento.</p>
56
57 <p>Esta compañía no venderá, cederá ni distribuirá la información personal que
58 es recopilada sin su consentimiento, salvo que sea requerido por un juez con
59 un orden judicial.</p>
60
61 <p>TestOne Se reserva el derecho de cambiar los términos de la presente
62 Política de Privacidad en cualquier momento.</p>
63 </ion-content>

Gist¹⁰
Fantástico. Ya tenemos la plantilla correspondiente a nuestra página. Sin embargo, no la hemos
ubicado aún en una pestaña.
Regresemos al archivo tabs.ts
Al igual que lo que ocurre con las otras páginas, debemos agregar la sentencia import para la nueva
página que hemos creado:
import { PrivacyPage } from ‘../privacy/privacy’;
Debemos también agregar una nueva variable que hará referencia a la clase:
tab4Root: any = PrivacyPage;
Ahora, vayamos al archivo tabs.html y agreguemos la pestaña correspondiente.
<ion-tab [root]=”tab4Root” tabTitle=”Privacy” tabIcon=”chatbubbles”></ion-tab>
Si ahora ejecutamos ionic serve, podremos ver la nueva pestaña:
¹⁰https://gist.github.com/vihugarcia/8ae00162e2501f081b94a79192e21364
Capítulo 1: ¿Qué es IONIC? 25

Nueva Pestaña

Sin embargo, si intentamos seleccionarla, veremos que se producen errores:


Capítulo 1: ¿Qué es IONIC? 26

Errores

Aquí es donde la información que obtenemos de la consola de depuración comienza o mostrar su


valor.
Podemos ver que la primera entrada en la lista de errores nos señala:
error_handler.js:47 EXCEPTION: Error in ./Tabs class Tabs - inline template:0:43 caused by: No
component factory found for PrivacyPage
Esto ocurre, porque si bien hemos creado nuestro componente de manera correcta, y hemos utilizado
el import correspondiente en el archivo tabs.ts, nuestra aplicación no sabe aún cómo construir un
componente PrivacyPage.
Vayamos al directorio app, y dentro de él busquemos y abramos el archivo app.module.ts. Su
contenido actualmente es el siguiente:

1 import { NgModule, ErrorHandler } from '@angular/core';


2 import { IonicApp, IonicModule, IonicErrorHandler } from 'ionic-angular';
3 import { MyApp } from './app.component';
4 import { AboutPage } from '../pages/about/about';
5 import { ContactPage } from '../pages/contact/contact';
6 import { HomePage } from '../pages/home/home';
7 import { TabsPage } from '../pages/tabs/tabs';
8
9 @NgModule({
10 declarations: [
11 MyApp,
12 AboutPage,
Capítulo 1: ¿Qué es IONIC? 27

13 ContactPage,
14 HomePage,
15 TabsPage
16 ],
17 imports: [
18 IonicModule.forRoot(MyApp)
19 ],
20 bootstrap: [IonicApp],
21 entryComponents: [
22 MyApp,
23 AboutPage,
24 ContactPage,
25 HomePage,
26 TabsPage
27 ],
28 providers: [{provide: ErrorHandler, useClass: IonicErrorHandler}]
29 })
30 export class AppModule {}

Este archivo, representa a toda nuestra aplicación como un módulo. Cada componente que es
utilizado en nuestro proyecto, debe ser declarado aquí de lo contrario nuestra aplicación no se dará
por enterada de que existe y obtendremos un error como el que se ha presentado.
Como podemos ver, existen cuatro sentencias import para cada una de nuestras páginas:

1 import { AboutPage } from '../pages/about/about';


2 import { ContactPage } from '../pages/contact/contact';
3 import { HomePage } from '../pages/home/home';
4 import { TabsPage } from '../pages/tabs/tabs';

Debemos agregar la sentencia correspondiente a nuestra nueva página. Tendremos ahora:

1 import { AboutPage } from '../pages/about/about';


2 import { ContactPage } from '../pages/contact/contact';
3 import { HomePage } from '../pages/home/home';
4 import { PrivacyPage } from '../pages/privacy/privacy';
5 import { TabsPage } from '../pages/tabs/tabs';

Además, debemos incluirla en las secciones declarations y entryComponents. Para estar completa-
mente seguros, aquí está todo el código del archivo con las modificaciones realizadas:
Capítulo 1: ¿Qué es IONIC? 28

1 import { NgModule, ErrorHandler } from '@angular/core';


2 import { IonicApp, IonicModule, IonicErrorHandler } from 'ionic-angular';
3 import { MyApp } from './app.component';
4 import { AboutPage } from '../pages/about/about';
5 import { ContactPage } from '../pages/contact/contact';
6 import { HomePage } from '../pages/home/home';
7 import { PrivacyPage } from '../pages/privacy/privacy';
8 import { TabsPage } from '../pages/tabs/tabs';
9
10 @NgModule({
11 declarations: [
12 MyApp,
13 AboutPage,
14 ContactPage,
15 HomePage,
16 PrivacyPage,
17 TabsPage
18 ],
19 imports: [
20 IonicModule.forRoot(MyApp)
21 ],
22 bootstrap: [IonicApp],
23 entryComponents: [
24 MyApp,
25 AboutPage,
26 ContactPage,
27 HomePage,
28 PrivacyPage,
29 TabsPage
30 ],
31 providers: [{provide: ErrorHandler, useClass: IonicErrorHandler}]
32 })
33 export class AppModule {}

Como habíamos mencionado anteriormente, si el servidor se está ejecutando aún, podremos ver que
los cambios que realicemos se reflejarán en el navegador.
Si ahora nos dirigimos a la nueva pestaña, podremos visualizarla correctamente:
Capítulo 1: ¿Qué es IONIC? 29

Página Privacidad

¡Fantástico!
Realicemos algunos cambios más a nuestra página recientemente creada.
Si analizamos el código, veremos que tenemos dos ocurrencias del mismo texto: Política de
Privacidad.
Vamos a quitar esa redundancia.
En el archivo privacy.ts, agreguemos una variable de tipo String de la siguiente manera:

1 titulo: String = 'Política de Privacidad';

Ahora, en el archivo privacy.html, reemplacemos el ion-title por lo siguiente:

1 <ion-title>{{titulo}}</ion-title>

Y en el contenido, reemplacemos el h2:

1 <h2>{{titulo}}</h2>

Las llaves dobles indican que todo lo que se encuentre entre ellas debe ser evaluado como una
expresión.
Capítulo 1: ¿Qué es IONIC? 30

En este caso, la variable titulo será reemplazada por su valor por lo que la salida es exactamente la
misma.
En muy poco tiempo, hemos aprendido mucho. Hemos creado un proyecto, hemos visto cómo se
estructuran las páginas de una aplicación, como puede crearse una nueva página y cómo debe
agregarse su declaración para que sea reconocida por la aplicación.
En el próximo capítulo comenzaremos con un nuevo proyecto que desarrollaremos hasta obtener
una aplicación totalmente funcional.
Estamos ya en la senda para crear aplicaciones móviles espectaculares.
Capítulo 2: Primera Aplicación
Completa
Vamos a una construir una aplicación para gestionar contactos. Podremos agregar, editar y eliminar
contactos.
En un primer momento nuestros datos estarán en memoria, pero luego los obtendremos a partir de
servicios web. Adquiriremos así los fundamentos necesarios para realizar aplicaciones complejas.
Cuando comenzamos con el desarrollo de una aplicación (sea móvil o no) es muy útil contar con
bosquejos de las pantallas que el usuario encontrará. Esto es muy útil en la comunicación con
el cliente ya que nos permite capturar requisitos de una manera rápida y eliminar potenciales
problemas antes de que se presenten.
Existen muchas herramientas para realizar sketchs de pantallas. Algunas incluyen funcionalidad
avanzada como por ejemplo la simulación del funcionamiento de una aplicación proporcionando la
posibilidad de interactuar con las pantallas.
No es necesario que incurramos en grandes costos para adquirir un software tan avanzado.
Particularmente yo uso una aplicación denominada Pencil¹¹. Es una herramienta de prototipado
de código abierto, totalmente gratuita, y que proporciona muy buenos resultados.
Por supuesto, no es obligatorio que utilicemos un programa para realizar el diseño de pantallas,
simplemente es algo recomendable. El tiempo que invirtamos en esta actividad será más que
compensado por la comprensión de las necesidades del cliente que obtendremos.
El programa tiene distintas colecciones de elementos de interface que podemos utilizar para diseñar
las pantallas. Por ejemplo en la figura siguiente se muestra la sección correspondiente a Android:
¹¹http://pencil.evolus.vn/
Capítulo 2: Primera Aplicación Completa 32

Android

También existe una sección denominada Desktop – Sketchy UI, cuyos tienen una apariencia de
dibujo a mano alzada, lo que resalta el concepto de que se trata de un diseño preliminar y esto útil
en el trato con los clientes.
Sea cual sea el grupo de componentes que se utilice, siempre debemos señalar a nuestros clientes
que el diseño presentado es simplemente un bosquejo a fin de poder plasmar y discutir ideas.
El primer diseño de pantalla es el siguiente:
Capítulo 2: Primera Aplicación Completa 33

Pantalla Inicial

Muy bien. Con este sencillo diseño como guía, podemos comenzar a trabajar en nuestra aplicación.

Creación del Proyecto


Vamos a crear un nuevo proyecto, pero esta vez el comando que utilizaremos será:
ionic start contactMgrApp blank –v2
La diferencia con el proyecto anterior es que al utilizar el modificador blank, estamos indicando
que en este caso deseamos una aplicación basada en una plantilla básica, que contendrá una única
página.
A partir de este lienzo en blanco desarrollaremos la aplicación.
Como en el proyecto anterior, debemos reemplazar la sección scripts del archivo package.json por
lo siguiente:
Capítulo 2: Primera Aplicación Completa 34

1 "scripts": {
2 "build": "ionic-app-scripts build",
3 "watch": "ionic-app-scripts watch",
4 "serve:before": "watch",
5 "emulate:before": "build",
6 "deploy:before": "build",
7 "build:before": "build",
8 "run:before": "build"
9 },

Al ejecutar ionic serve debemos ver la siguiente pantalla:

Primera pantalla

Tenemos una única página con un poco de texto.


Abramos el proyecto con nuestro editor para ver la estructura.
Podemos comprobar que dentro del directorio pages existe una única carpeta denominada home.
Dejémosla tal como está.

Creando una nueva página


Vamos a crear una nueva página ejecutando:
ionic g page contactos
Obtendremos un nuevo directorio denominado contactos.
El contenido del archivo contactos.ts es el siguiente:
Capítulo 2: Primera Aplicación Completa 35

1 import { Component } from '@angular/core';


2 import { NavController, NavParams } from 'ionic-angular';
3
4 /*
5 Generated class for the Contactos page.
6
7 See http://ionicframework.com/docs/v2/components/#navigation for more info on
8 Ionic pages and navigation.
9 */
10 @Component({
11 selector: 'page-contactos',
12 templateUrl: 'contactos.html'
13 })
14 export class ContactosPage {
15
16 constructor(public navCtrl: NavController, public navParams: NavParams) {}
17
18 ionViewDidLoad() {
19 console.log('ionViewDidLoad ContactosPage');
20 }
21
22 }

Como primera medida, agregaremos una variable para contener el título que mostraremos en la
página:
Luego de export class ContactosPage { agregamos:

1 titulo: String = 'Administrador de Contactos';

El contenido de contactos.html es el siguiente:

1 <!--
2 Generated template for the Contactos page.
3
4 See http://ionicframework.com/docs/v2/components/#navigation for more info on
5 Ionic pages and navigation.
6 -->
7 <ion-header>
8
9 <ion-navbar>
10 <ion-title>contactos</ion-title>
Capítulo 2: Primera Aplicación Completa 36

11 </ion-navbar>
12
13 </ion-header>
14
15
16 <ion-content padding>
17
18 </ion-content>

Comenzaremos por utilizar la variable que hemos creado en el ion-title y agregar una etiqueta h2
en el contenido de la página.
Ahora tendremos: Del libro:

1 <!--
2 Generated template for the Contactos page.
3
4 See http://ionicframework.com/docs/v2/components/#navigation for more info on
5 Ionic pages and navigation.
6 -->
7 <ion-header>
8
9 <ion-navbar>
10 <ion-title>{{titulo}}</ion-title>
11 </ion-navbar>
12
13 </ion-header>
14
15
16 <ion-content padding>
17 <h2>{{titulo}}</h2>
18 </ion-content>

Gist¹²
Perfecto. Sin embargo, tal como está ahora la aplicación, no tenemos modo de ver la página creada.
Vamos a modificar el proyecto de modo que la página que se cargue al comienzo sea la nueva página
en lugar de la página que viene por defecto.
¹²https://gist.github.com/vihugarcia/2882bb440b1bbd6cccaad24cffee4751
Capítulo 2: Primera Aplicación Completa 37

Seteando la página raíz


Si examinamos el archivo app.component.ts dentro del directorio app, encontraremos la siguiente
línea:
rootPage = HomePage;
Esta es la línea que indica que la página Home es la que debe tomarse como raíz. Antes de modificar
esta línea, sin embargo, debemos agregar la sentencia import para poder usar la nueva página:
import { ContactosPage } from ‘../pages/contactos/contactos’;
Ahora podemos indicar la nueva raíz:
rootPage = ContactosPage;
Sin embargo, si ahora ejecutamos ionic serve obtendremos un error. Si recordamos del capítulo
anterior, esto sucede porque debemos informarle a la aplicación sobre la nueva página creada. Esto
lo hacemos editando el archivo app.module.ts

1 import { ContactosPage } from '../pages/contactos/contactos';

Además, debemos agregar la página en las secciones declarations y entryComponents.


El contenido completo del archivo quedará de la siguiente manera.
Del libro:

1 import { NgModule, ErrorHandler } from '@angular/core';


2 import { IonicApp, IonicModule, IonicErrorHandler } from 'ionic-angular';
3 import { MyApp } from './app.component';
4 import { HomePage } from '../pages/home/home';
5 import { ContactosPage } from '../pages/contactos/contactos';
6
7 @NgModule({
8 declarations: [
9 MyApp,
10 HomePage,
11 ContactosPage
12 ],
13 imports: [
14 IonicModule.forRoot(MyApp)
15 ],
16 bootstrap: [IonicApp],
17 entryComponents: [
18 MyApp,
Capítulo 2: Primera Aplicación Completa 38

19 HomePage,
20 ContactosPage
21 ],
22 providers: [{provide: ErrorHandler, useClass: IonicErrorHandler}]
23 })
24 export class AppModule {}

Gist¹³
Ahora lancemos ionic serve.

Nueva pantalla inicio

Nada extraordinario, pero seguimos avanzando.

Creando un modelo de datos


Sabemos que nuestra aplicación mostrará un listado de contactos. Es decir, tenemos una entidad
fundamental denominada contacto.
Esta entidad tendrá atributos tales como nombre y dirección, y algún otro que sirva como
identificador.
En nuestra aplicación, tendremos que modelar todas las entidades que la conformen. Es decir, por
cada entidad que componga nuestra aplicación será conveniente construir un modelo de datos. No
se preocupen, suena más complicado de lo que en realidad es.
Creemos al mismo nivel que el directorio pages una carpeta denominada model. No es obligatorio
que agrupemos todos nuestros modelos en un único directorio, pero es una práctica recomendable
ya que nos permite mantener nuestra aplicación ordenada y mantenible. Dentro de la carpeta model
creemos un archivo denominado contacto.ts.
Agreguemos a dicho archivo el siguiente contenido.
Del libro:
¹³https://gist.github.com/vihugarcia/e1d7aad010c37e09ec0be903891e7d7a
Capítulo 2: Primera Aplicación Completa 39

1 export class Contacto{


2 constructor(
3 public id: number,
4 public nombre: string,
5 public direccion: string
6 ){}
7 }

Gist¹⁴
Estamos definiendo una clase denominada Contacto con tres propiedades o atributos: id, nombre,
y dirección. Nótese que cada una de estas propiedades está precedida del modificador public. Esto
señala que los valores de dichas propiedades pueden ser accedidos desde afuera de la clase.
Si no se indica el nivel de visibilidad de una propiedad, se considera que es public. Es decir:
public id: number es equivalente a id: number.
Habiendo definido la clase Contacto, podemos hacer ya uso de ella.
En el archivo contactos.ts agreguemos el siguiente import:

1 import { Contacto } from '../../model/contacto';

Ahora definamos una variable que contenga algunas instancias de la clase definida:

1 public contactos = [
2 new Contacto(1, "Andrea Gómez", "Calle Uno 123"),
3 new Contacto(2, "Juan Perez", "Calle Dos 567"),
4 new Contacto(3, "Martín Álvarez", "Calle del Pueblo 628")
5 ];

Lo que estamos haciendo, es definir un arreglo que contiene objetos del tipo Contacto. En el momento
de crear los objetos (con la sentencia new) le pasamos los parámetros que espera el constructor de
la clase Contacto.
Debemos ahora modificar el archivo contactos.html para mostrar los datos de los contactos que
hemos creado.

Listas y estructuras repetitivas


Podemos hacerlo utilizando una lista de ítems. Para ello ionic nos brinda un componente denomi-
nado ion-list. Dentro de cada ion-list existirán ítems. Cada ítem corresponderá a un contacto.
Luego de la etiqueta h2 agreguemos lo siguiente.
Del libro:
¹⁴https://gist.github.com/vihugarcia/af8bf72b86aaa1dbd1d03261bdd096c4
Capítulo 2: Primera Aplicación Completa 40

1 <ion-list>
2 <ion-item *ngFor="let contacto of contactos">
3 <ion-label>{{contacto.nombre}}</ion-label>
4 <ion-label>{{contacto.direccion}}</ion-label>
5 </ion-item>
6 </ion-list>

Gist¹⁵
La lista está comprendida entre las etiquetas <ion-list> y </ion-list>
Cada ítem queda determinado por el par de etiquetas <ion-item> y </ion-item>
Dentro de ion-item, tenemos la siguiente sentencia:
*ngFor=”let contacto of contactos”
La directiva *ngFor nos permite utilizar un ciclo for para repetir un elemento. En ese caso, como
lo que queremos es repetir tantos ítems como contactos, agregamos la sentencia en la etiqueta de
apertura <ion-item>
Dentro del *ngFor, estamos definiendo una variable ** contacto **. La variable contacto tomará un
valor distinto en cada iteración del ciclo. Es decir, por cada elemento del arreglo contactos, la variable
** contacto ** tomará dicho valor. Cada uno de dichos elementos es en este caso un objeto.
Ejecutemos ahora ionic serve. La salida que obtenemos es la siguiente:

Lista de Contactos

Estamos más cerca. Tenemos nuestra lista de contactos. Lógicamente, una aplicación de contactos
real necesitará manejar más datos que el nombre y la dirección. Podemos agregar tantos como sea
necesario en la definición de nuestra clase Contacto.
Por ejemplo, agreguemos teléfono y dirección, ambos como tipo String.
La nueva definición de la clase quedará de la siguiente manera.
Del libro:
¹⁵https://gist.github.com/vihugarcia/f847ea364b2eba7d56097fcdeedbc6f3
Capítulo 2: Primera Aplicación Completa 41

1 export class Contacto{


2 constructor(
3 public id: number,
4 public nombre: string,
5 public direccion: string,
6 public telefono: string,
7 public email: string
8 ){}
9 }

Gist¹⁶
Sin embargo, ahora nos encontraremos con que la línea de comandos nos muestra mensajes de error:

Error de número de parámetros

Esto ocurre porque ahora el constructor de la clase espera cinco parámetros, pero en el momento de
crear los objetos en el archivo contactos.ts, estamos proporcionando tres. Corrijamos.

¹⁶https://gist.github.com/vihugarcia/f572f4db6387a8d9f4b6d3cdc71ea1a8
Capítulo 2: Primera Aplicación Completa 42

1 public contactos = [
2 new Contacto(1, "Andrea Gómez", "Calle Uno 123", "12345", "gomez@gmail.com"),
3 new Contacto(2, "Juan Perez", "Calle Dos 567", "23456", "perez@gmail.com"),
4 new Contacto(3, "Martín Álvarez", "Calle del Pueblo 628", "34567",
5 "alvarez@gmail.com")
6 ];

Los errores han desaparecido y ahora nuestra aplicación funciona nuevamente.


En la pantalla principal, se siguen mostrando como únicos datos el nombre y dirección. Lo que
haremos ahora es agregar la opción para permitir ver los detalles de un contacto seleccionado.
Una forma elegante de agregar opciones para trabajar con cada uno de los ítems, muy utilizada en
las apps, es la de presentar una serie de botones al deslizar el ítem ya sea a izquierda o derecha.
Para ello, modifiquemos la ion-list de la siguiente manera.
Del libro:

1 <ion-list>
2 <ion-item-sliding *ngFor="let contacto of contactos">
3 <ion-item>
4 <ion-label>{{contacto.nombre}}</ion-label>
5 <ion-label>{{contacto.direccion}}</ion-label>
6 </ion-item>
7
8 <ion-item-options side="right">
9 <button ion-button color="favorite" (click)="verContacto(contacto)">
10 <ion-icon name="eye"></ion-icon>
11 Ver
12 </button>
13 </ion-item-options>
14 </ion-item-sliding>
15 </ion-list>

Gist¹⁷
Las diferencias son las siguientes:
Se utiliza un ion-item-sliding como grupo repetitivo en lugar de ion-item. Este es el elemento que
nos permitirá agregar un conjunto de botones para cada ítem.
Luego tenemos el ion-list donde se muestran los datos del contacto.

¹⁷https://gist.github.com/vihugarcia/5ceeee3868c76b4cf6513689095e9f1e
Capítulo 2: Primera Aplicación Completa 43

1 <ion-item>
2 <ion-label>{{contacto.nombre}}</ion-label>
3 <ion-label>{{contacto.direccion}}</ion-label>
4 </ion-item>

A continuación, añadimos un elemento ion-item-options. La propiedad side determina la alineación


de los botones. En este caso los botones aparecerán a la derecha cuando se realice un deslizamiento
de derecha a izquierda.

1 <ion-item-options side="right">
2 <button ion-button color="favorite" (click)="verContacto(contacto)">
3 <ion-icon name="eye"></ion-icon>
4 Ver
5 </button>
6 </ion-item-options>

En este caso se mostrará un solo botón. Al hacer clic en este botón se convocará a un método
verContacto que recibe como parámetro un objeto del tipo Contacto.
Escribamos dicho método. Por ahora estará vacío.

1 verContacto(contacto: Contacto) {
2
3 }

El resultado es el siguiente:

Botón ver

Vamos a crear ahora la página que mostrará los detalles del contacto seleccionado. El comando para
crear una nueva página ya debe resultarnos familiar.
Capítulo 2: Primera Aplicación Completa 44

ionic g page contacto


Concentrémonos ahora en el archivo contacto.html. Modifiquémoslo de la siguiente manera. Del
libro:

1 <!--
2 Generated template for the Contacto page.
3
4 See http://ionicframework.com/docs/v2/components/#navigation for more info on
5 Ionic pages and navigation.
6 -->
7 <ion-header>
8
9 <ion-navbar>
10 <ion-title>Contacto</ion-title>
11 </ion-navbar>
12
13 </ion-header>
14
15
16 <ion-content padding>
17 <div *ngIf="contacto">
18 <ion-card>
19 <ion-card-content>
20 <ion-card-title>
21 {{contacto.nombre}}
22 </ion-card-title>
23 <p>{{contacto.direccion}}</p>
24 <p>{{contacto.telefono}}</p>
25 <p>{{contacto.email}}</p>
26 </ion-card-content>
27 </ion-card>
28 </div>
29 </ion-content>

Gist¹⁸
Muy bien. Hay unas cuantas cosas interesantes en el fragmento de código anterior.
Por una lado, tenemos la siguiente etiqueta: <div *ngIf=”contacto”>
Estamos usando aquí una estructura de control. Un if, que en IONIC representamos con *ngIf.
Esta estructura de control, evalúa una condición y devuelve verdadero o falso. Al agregarla al
¹⁸https://gist.github.com/vihugarcia/d0a4ab385ca285a9d8cea16ee19a7ddf
Capítulo 2: Primera Aplicación Completa 45

elemento div, estamos indicando que deseamos visualizar dicho elemento sólo cuando la condición
sea verdadera. En este caso, cuando exista un objeto contacto a mostrar.
Luego introducimos un nuevo elemento de IONICS. Una ion-card.
Este elemento, permite presentar contenido en un bloque que tiene una bonita presentación y cuya
apariencia podemos configurar.
Una ion-card se compone de un ion-card-header y de un ion-card-content. Un encabezado y un
cuerpo respectivamente.
Cada vez que tengamos dudas sobre el uso de un componente, o querramos ampliar nues-
tros conocimientos, podemos recurrir a la muy buena documentación de IONIC disponible en
https://ionicframework.com/docs/v2/components/¹⁹
Existen multitud de ejemplos y además tenemos se nos presentan simulaciones de aplicaciones donde
podemos ver los ejemplos ejecutándose en vivo. No podría ser mejor.
Ahora que tenemos la página de un contacto individual creada, regresemos a contactos.ts y
completemos el método verContacto.

1 verContacto(contacto: Contacto) {
2 this.navCtrl.push(ContactoPage, {contacto});
3 }

Analicemos lo que ocurre aquí. En toda aplicación móvil, las páginas se muestran en un stack (pila).
Una nueva página puede apilarse encima de las páginas existentes, utilizando una operación push.
Para ello, invocamos al método push de un objeto del tipo NavController. Como habremos notado,
cuando creamos una página mediante la línea de comandos, ionic se encarga de pasar como
parámetro al método constructor de la página un objeto navCtrl del tipo NavController.
El método push tiene la forma: push(página, {parámetros})
En este caso, estamos indicando que debe apilarse la página ContactoPage, y que esta recibirá como
parámetro un objeto de tipo Contacto.
Para que esto funcione, debemos agregar el import de la página contacto en la parte superior.
Para estar seguros, aquí está el código completo de contactos.ts
Del libro:

¹⁹https://ionicframework.com/docs/v2/components/
Capítulo 2: Primera Aplicación Completa 46

1 import { Component } from '@angular/core';


2 import { NavController, NavParams } from 'ionic-angular';
3 import { Contacto } from '../../model/contacto';
4 import { ContactoPage } from '../contacto/contacto';
5
6 /*
7 Generated class for the Contactos page.
8
9 See http://ionicframework.com/docs/v2/components/#navigation for more info on
10 Ionic pages and navigation.
11 */
12 @Component({
13 selector: 'page-contactos',
14 templateUrl: 'contactos.html'
15 })
16 export class ContactosPage {
17 titulo: String = 'Administrador de Contactos';
18 public contactos = [
19 new Contacto(1, "Andrea Gómez", "Calle Uno 123", "12345", "gomez@gmail.com"),
20 new Contacto(2, "Juan Perez", "Calle Dos 567", "23456", "perez@gmail.com"),
21 new Contacto(3, "Martín Álvarez", "Calle del Pueblo 628", "34567",
22 "alvarez@gmail.com")
23 ];
24
25 constructor(public navCtrl: NavController, public navParams: NavParams) {}
26
27 ionViewDidLoad() {
28 console.log('ionViewDidLoad ContactosPage');
29 }
30
31 verContacto(contacto: Contacto) {
32 this.navCtrl.push(ContactoPage, {contacto});
33 }
34
35 }

Gist²⁰
El parámetro pasado a la página contacto, tiene el nombre contacto, y recibe el objeto contacto que
está en la declaración del método verContacto.
Cuando el parámetro tiene el mismo nombre que el objeto que recibe, se puede utilizar una notación
abreviada como la de arriba. Equivalente a lo anterior sería:
²⁰https://gist.github.com/vihugarcia/2aa608d26ad04d931039daf21ee58e9c
Capítulo 2: Primera Aplicación Completa 47

this.navCtrl.push(ContactoPage, {contacto: contacto});


Ahora, el parámetro enviado debe recibirse. Aquí está el código completo de contacto.ts.
Del libro:

1 import { Component } from '@angular/core';


2 import { NavController, NavParams } from 'ionic-angular';
3
4 import { Contacto } from '../../model/contacto';
5
6 /*
7 Generated class for the Contacto page.
8
9 See http://ionicframework.com/docs/v2/components/#navigation for more info on
10 Ionic pages and navigation.
11 */
12 @Component({
13 selector: 'page-contacto',
14 templateUrl: 'contacto.html'
15 })
16 export class ContactoPage {
17 public contacto: Contacto;
18
19 constructor(public navCtrl: NavController, public navParams: NavParams) {
20 this.contacto = this.navParams.get('contacto');
21 }
22
23 ionViewDidLoad() {
24 console.log('ionViewDidLoad ContactoPage');
25 }
26
27 }

Gist²¹
Estupendo. Ejecutemos la aplicación para ver los resultados:
²¹https://gist.github.com/vihugarcia/fb76df64154d601804af89c24f20bce9
Capítulo 2: Primera Aplicación Completa 48

Detalle de Contacto

Nada mal.
Vamos a agregar ahora botones con opciones para editar y eliminar un contacto.
Para ello, deberemos modificar el archivo contactos.html de la siguiente manera.
Del libro:

1 <button ion-button color="favorite" (click)="verContacto(contacto)">


2 <ion-icon name="eye"></ion-icon>
3 Ver
4 </button>
5 <button ion-button color="dark" (click)="editarContacto(contacto)">
6 <ion-icon name="create"></ion-icon>
7 Editar
8 </button>
9 <button ion-button color="danger" (click)="eliminarContacto(contacto)">
10 <ion-icon name="remove-circle"></ion-icon>
11 Eliminar
12 </button>

Gist²²
Se han resaltado en negrita las partes a agregar.
Debemos agregar los dos métodos que se invocan en contactos.ts. Por ahora dejaremos en suspenso
su implementación. Simplemente escribiremos:

²²https://gist.github.com/vihugarcia/239061523f3b3f2d3c12a84aa97ef8e5
Capítulo 2: Primera Aplicación Completa 49

1 editarContacto(contacto: Contacto) {
2
3 }
4
5 eliminarContacto(contacto: Contacto) {
6
7 }

Con los cambios realizados, podremos ver que ahora al deslizar la pantalla hacia le derecha sobre
un contacto, visualizamos tres botones.

Nuevos botones

En este punto, y antes de avanzar con el resto de la funcionalidad de la aplicación, vamos a tomarnos
un breve tiempo para mejorar la apariencia de la misma.
Vayamos al archivo variables.scss dentro del directorio theme y reemplacemos el mapa $colors por
lo siguiente.
Del libro:

1 $colors: (
2 primary: #638CA6,
3 secondary: #32db64,
4 favorite: #17A697,
5 danger: #D93240,
6 light: #BFD4D9,
7 dark: #F2671F
8 );

Gist²³
²³https://gist.github.com/vihugarcia/a3be82bd10e372c3da12eae4934761ff
Capítulo 2: Primera Aplicación Completa 50

Ahora indiquemos tanto en contactos.html como en contacto.html que nuestra barra de navegación
debe usar el color primary.
<ion-navbar color=”primary”>
La apariencia de nuestra aplicación debería ser ahora la siguiente:

Nuevos colores

Creo que se ve mejor. Siéntanse en libertad de usar cualquier combinación de colores que sea de su
agrado.
Algo que estamos echando en falta, es la posibilidad de agregar un nuevo contacto. Vamos a
comenzar a resolver el problema.
Para ello, añadamos en contactos.html luego de la etiqueta de cierre </ion-content> lo siguiente:

1 <ion-fab right bottom>


2 <button ion-fab (click)="mostrarAgregarContacto()">
3 <ion-icon name="add"></ion-icon>
4 </button>
5 </ion-fab>

Debemos añadir también el método correspondiente en contactos.ts:

1 mostrarAgregarContacto() {
2
3 }

Podemos ver que ahora nuestra pantalla principal presenta un botón en la esquina inferior derecha.
Capítulo 2: Primera Aplicación Completa 51

Botón agregar contacto

Desde luego, el botón no hace nada aún. Mejor dicho, convoca a un método que todavía no realiza
ninguna función.
Vamos a resolver eso.

Crear una ventana modal


Vamos a crear ahora una nueva página
ionic g page add-contacto-modal
Esta página, que mostraremos de forma modal, contendrá un formulario que nos permitirá agregar
un nuevo contacto.
Capítulo 2: Primera Aplicación Completa 52

Editemos el archivo add-contacto-modal.html y agreguemos lo siguiente dentro del elemento ion-


content.
Del libro:

1 <form #formContacto="ngForm" class="container" (ngSubmit)="onSubmit()">


2 <ion-item>
3 <ion-input name="nombre" id="nombre" #nombre="ngModel"
4 [(ngModel)]="contacto.nombre" required="required" placeholder="Nombre">
5 </ion-input>
6 </ion-item>
7 <ion-item danger [hidden]="nombre.valid || nombre.untouched">
8 El nombre es obligatorio
9 </ion-item>
10
11 <ion-item>
12 <ion-input name="direccion" id="direccion"
13 #direccion="ngModel" [(ngModel)]="contacto.direccion"
14 required="required" placeholder="Dirección"></ion-input>
15 </ion-item>
16 <ion-item danger [hidden]="direccion.valid || direccion.untouched">
17 La dirección es obligatoria
18 </ion-item>
19
20 <ion-item>
21 <ion-input name="telefono" id="telefono"
22 #telefono="ngModel" [(ngModel)]="contacto.telefono"
23 required="required" placeholder="Teléfono"></ion-input>
24 </ion-item>
25 <ion-item danger [hidden]="telefono.valid || telefono.untouched">
26 El teléfono es obligatorio
27 </ion-item>
28
29 <ion-item>
30 <ion-input name="email" id="email" #email="ngModel"
31 [(ngModel)]="contacto.email" placeholder="E-Mail"></ion-input>
32 </ion-item>
33
34 <div class="submit-button">
35 <button ion-button block
36 type="submit" [disabled]="!formContacto.form.valid">Enviar</button>
37 </div>
38
Capítulo 2: Primera Aplicación Completa 53

39 </form>

Gist²⁴
Varias cosas suceden aquí.
En primer lugar, estamos añadiendo un formulario con las etiquetas <form> y </form>. Esto
es exactamente igual a cuando creamos un formulario en una página web común y corriente.
#formContacto=”ngForm” agrega un identificador a nuestro formulario, y además le indica a IONIC
que se trata de un ngForm, lo que le agrega al formulario una serie de capacidades muy interesantes.
(ngSubmit)=”onSubmit()” intercepta el evento submit del formulario (representado por ngSubmit) y
le asigna un método a ser ejecutado, en este caso onSubmit.
Luego tenemos varios inputs que están contenidos dentro de respectivos ion-item. Como ven, un
ion-item no tiene que estar forzosamente asociado a un ion-list.
Un cuadro de texto en IONIC se corresponde con el elemento ion-input. Este tiene las mismas
características que un input ordinario, pero está específicamente pensado para aplicaciones móviles
por lo que debemos utilizarlo.
Si analizamos el primer ion-input podemos ver las propiedades required=”required” placehol-
der=”Nombre”
required es un atributo html5 que indica que el ingreso de datos es obligatorio. Un placeholder es un
texto que se muestra dentro del cuadro de texto para indicar al usuario qué dato debe ingresar. Este
texto desaparece en cuanto el usuario comienza a escribir y reaparece si el usuario borra el texto.
Analicemos ahora lo siguiente: #nombre=”ngModel” [(ngModel)]=”contacto.nombre”

1 #nombre="ngModel"

Asigna un identificador al cuadro de texto, y le indica a IONIC que se trata de un campo


correspondiente a un modelo de datos.
[(ngModel)]=”contacto.nombre” es la forma de indicar un databinding.
¿Qué es eso de data binding?
Vamos a ver un ejemplo ahora mismo antes de continuar avanzando.
En el archivo contactos.html, justo debajo de la etiqueta h2, agregue lo siguiente:

²⁴https://gist.github.com/vihugarcia/2081d84a3c97f3862024e43f3a4a49aa
Capítulo 2: Primera Aplicación Completa 54

1 <p>{{texto}}</p>
2
3 <ion-item>
4 <ion-input type="text" placeholder="escriba algo..." [(ngModel)]="texto">
5 </ion-input>
6 </ion-item>

Verán la siguiente pantalla:

Data binding

Si ahora comienzan a escribir en el cuadro de texto que ha agregado, verán que el texto aparece
arriba del mismo.
Capítulo 2: Primera Aplicación Completa 55

Data binding

Esto se debe a que estamos utilizando databinding.


Cuando escribimos [(ngModel)]=”texto” enlazamos el texto que contiene el ion-input con el valor
de una variable texto.
Justo arriba del cuadro de texto tenemos <p>{{texto}}</p>
Las llaves doblen indican que lo que está entre ellas debe evaluarse como una expresión. Cuando
escribimos un texto, el valor de la variable texto se modifica y este cambio automáticamente se ve
reflejado. Angular (que está detrás de IONIC) se encarga de modificar el DOM (Document Object
Model) de forma transparente para nosotros.
No se preocupen si no ha quedado claro del todo. Lo iremos comprendiendo cada vez mejor.
Ahora continuemos.
El contenido del archivo add-contacto-modal.ts actualmente es el siguiente:

1 import { Component } from '@angular/core';


2 import { NavController, NavParams } from 'ionic-angular';
3
4 /*
5 Generated class for the AddContactoModal page.
6
7 See http://ionicframework.com/docs/v2/components/#navigation for more info on
8 Ionic pages and navigation.
9 */
10 @Component({
11 selector: 'page-add-contacto-modal',
12 templateUrl: 'add-contacto-modal.html'
Capítulo 2: Primera Aplicación Completa 56

13 })
14 export class AddContactoModalPage {
15
16 constructor(public navCtrl: NavController, public navParams: NavParams) {}
17
18 ionViewDidLoad() {
19 console.log('ionViewDidLoad AddContactoModalPage');
20 }
21
22 }

Como primera medida, vamos a cambiar el nombre de la clase de AddContactoModalPage a


AddContactoModal, ya que no pretendemos que funcione como una página ordinaria sino como
una ventana modal. No es obligatorio cambiarle el nombre, pero me parece prudente.
Ya que nuestro propósito es agregar un nuevo contacto, debemos importar nuestro modelo Contacto
a fin de poder usarlo.

1 import { Contacto } from '../../model/contacto';

Ahora declaremos una variable para contener una instancia vacía de nuestro modelo. public contacto
= new Contacto(0, ‘’, ‘’, ‘’, ‘’);
Nótese que debemos pasar los parámetros requeridos al constructor, aun cuando se trate de cero o
cadenas vacías, de lo contrarios obtendremos un error.
Perfecto. De regreso ahora a contactos.ts, debemos importar nuestra ventana modal para poder
usarla.
Luego cambiemos el import:

1 import { NavController, NavParams } from 'ionic-angular';

Por:

1 import { NavController, NavParams, ModalController } from 'ionic-angular';

Estamos incluyendo el componente necesario para mostrar una pantalla como modal.
Los argumentos del constructor ahora deben cambiar de:
constructor(public navCtrl: NavController, public navParams: NavParams)
a:
Capítulo 2: Primera Aplicación Completa 57

constructor(public navCtrl: NavController, public navParams: NavParams, public modalCtrl: Mo-


dalController)
Estamos declarando una nueva variable de tipo ModalController y la estamos haciendo pública, de
manera de poder utilizarla en métodos de la clase.
Ahora podemos reescribir el método que mostrará la pantalla modal.

1 mostrarAgregarContacto() {
2 let modal = this.modalCtrl.create(AddContactoModal);
3 modal.present();
4
5 modal.onDidDismiss(data => {});
6 }

No estamos en condiciones de ver todo en funcionamiento aún. Debemos agregar el import


correspondiente en app.module.ts.

1 import { AddContactoModal }
2 from '../pages/add-contacto-modal/add-contacto-modal';

Y agregar la clase en las secciones declarations y entryComponents.


Ahora sí, al presionar el botón agregar, veremos nuestra pantalla modal.

Ventana modal

Podemos descartar la pantalla presionando la tecla Escape.


Realicemos algunas modificaciones. En primer lugar, asignémosle el estilo a la barra de navegación.
Luego, creemos en el archivo add-contacto-modal.ts una variable titulo.
Capítulo 2: Primera Aplicación Completa 58

1 public titulo : String = 'Agregar Contacto';

Y utilicemos las llaves dobles para mostrar el valor de la variable en la plantilla. La barra de
navegación tendrá ahora el siguiente contenido:

1 <ion-navbar color="primary">
2 <ion-title>{{titulo}}</ion-title>
3 </ion-navbar>

Muy bien. Ahora tenemos que ocuparnos de otro detalle.


En estos momentos, estamos descartando la ventana modal mediante la tecla Escape. Vamos a
agregar un botón que se ocupe de esto.
Justo después del ion-title añadamos lo siguiente:

1 <ion-buttons start>
2 <button ion-button (click)="dismiss()">
3 <span secondary showWhen="ios">Cancelar</span>
4 <ion-icon name="md-close" showWhen="android, windows">
5 </ion-icon>
6 </button>
7 </ion-buttons>

ion-buttons es un componente que nos permite agrupar botones. Luego viene un componente button.
Nótese que tenemos dos formas alternativas de presentar el contenido del botón. Por un lado, cuando
el sistema operativo sea ios, se mostrará el texto Cancelar. Por otro, para android y Windows
mostraremos un ícono.
Podemos ver las diferencias a continuación:
Capítulo 2: Primera Aplicación Completa 59

Botón cerrar en Android

Botón cerrar en IOS

Muy bien. Hasta ahora tenemos lo siguiente:

• Una pantalla principal que muestra una lista de contactos. Al deslizar la pantalla de derecha
Capítulo 2: Primera Aplicación Completa 60

a izquierda estando sobre un contacto, se muestran tres botones con opciones para ver, editar
y eliminar el contacto.
– Al presionar el botón ver podemos acceder a una pantalla con los detalles del contacto
seleccionado.
– En la pantalla inicial, esquina inferior derecha, tenemos un botón que al ser presionado,
muestra una ventana modal con un formulario que permite agregar un nuevo contacto.

Sin embargo, la funcionalidad para agregar, editar y eliminar contactos aún no se encuentra
implementada.
Vamos a resolver esto.
Comenzaremos por permitir agregar un nuevo contacto.
En el archivo add-contacto-modal.ts cambiemos el import:

1 import { NavController, NavParams } from 'ionic-angular';

Por:

1 import { NavController, NavParams, ViewController } from 'ionic-angular';

Luego, agreguemos un nuevo parámetro al constructor. Debe quedar de la siguiente manera:


constructor(public navCtrl: NavController, public navParams: NavParams, public viewCtrl: View-
Controller) {}
En la plantilla, es decir en el archivo add-contacto-modal.html, interceptamos el envío del formulario
de la siguiente manera:
(ngSubmit)=”onSubmit()”
Nótese que la sintaxis para asociar una función a un evento es:
(evento)=”funcion()”
Aún no hemos implementado el evento onSubmit, vamos a hacerlo ahora. Nuevamente en add-
contacto-modal.ts.
Antes de generar la implementación real del método, vamos a realizar una prueba para asegurarnos
de que el formulario está funcionando de la manera deseada.
Agreguemos lo siguiente:

1 onSubmit() {
2 console.log(this.contacto);
3 }
Capítulo 2: Primera Aplicación Completa 61

Aquí mostraremos por consola el contacto con los valores recibidos para sus propiedades de parte
del formulario.
Ejecutemos la aplicación y presionemos el botón agregar. Veremos nuestra ventana modal.

Ventana modal

Nótese que el botón Enviar está desactivado. Sólo se activará cuando el formulario sea válido. Esto
lo logramos con las siguiente línea presente en el archivo add-contacto-modal.html:

1 <button ion-button block type="submit" [disabled]="!formContacto.form.valid">


2 Enviar</button>

Lo que hace el fragmento [disabled]=”!formContacto.form.valid” es enlazar el valor de la propiedad


disabled con el valor al que evalúa la expresión que se encuentra entre comillas.
Dicha expresión es !formContacto.form.valid. El signo de admiración que se encuentra delante
es el operador lógico de negación. formContacto.form.valid tomará un valor verdadero cuando el
formulario sea válido. Por lo tanto, al poner el operador de negación delante estamos haciendo que
la expresión evalúe a verdadero cuando el formulario NO sea válido.
Esto está bien, porque nosotros queremos deshabilitar el botón de envío exactamente cuando ocurre
eso.
El poder validar el formulario en tiempo real, es gracias a la magia de IONIC.
Recordemos que al crear el formulario, en su etiqueta de apertura, pusimos:

1 #formContacto="ngForm"

Habíamos mencionado que esto le indica a IONIC que debe tratar a este formulario como un
formulario especial al que le agrega funcionalidad extra.
Capítulo 2: Primera Aplicación Completa 62

Esta funcionalidad es la que estamos utilizando para habilitar o deshabilitar el botón de envío.
Continuemos.
Cuando se han completado todos los campos que hemos definido como obligatorios, el botón
cambiará a su estado habilitado como podemos ver a continuación:

Formulario completo

Si presionamos el botón ENVIAR, veremos el siguiente mensaje en la consola:

Contacto en consola

Podemos hacer clic en la flecha de la izquierda y desplegar los detalles:

Detalles de contacto

Como vemos nuestro formulario funciona correctamente. Una vez probado, podemos cambiar la
implementación del método onSubmit por la siguiente:
Capítulo 2: Primera Aplicación Completa 63

1 onSubmit() {
2 this.viewCtrl.dismiss(this.contacto);
3 }

Lo que estamos haciendo ahora es cerrar la vista o pantalla modal, utilizando el componente
ViewController, pero además estamos pasando como parámetro al método dismiss el contacto que
queremos incorporar.
Eso es todo lo que tenemos que hacer en add-contacto-modal.ts.
De regreso a contactos.ts
La implementación que tenemos del método que muestra la pantalla modal hasta ahora es la
siguiente:

1 mostrarAgregarContacto() {
2 let modal = this.modalCtrl.create(AddContactoModal);
3 modal.present();
4
5 modal.onDidDismiss(data => {});
6 }

Analicemos la línea:
modal.onDidDismiss(data ⇒ {})
Tenemos una variable (denominada modal) que referencia nuestra ventana normal. Cuando esta
ventana ejecuta el evento dismiss, nosotros lo capturamos para nuestros propósitos.
La variable data, representa un dato devuelto por la ventana modal. Recordemos que en el archivo
add-contacto-modal.ts nosotros pasábamos como parámetro al método dismiss el objeto conteniendo
el nuevo contacto a agregar.
Por lo tanto, data viene a representar ese contacto devuelto por la venta modal y que ahora podemos
manipular. El operador ⇒ indica que sobre ese dato devuelto operará una función. En este caso se
trata de una función vacía, representada por llaves sin contenido.
Para comprenderlo mejor: data ⇒ {} es conceptualmente equivalente function(data) {}. Es decir, data
es el parámetro de la función cuyo cuerpo está entre llaves.
Ahora cambiemos la implementación de todo el método por la siguiente.
Del libro:
Capítulo 2: Primera Aplicación Completa 64

1 mostrarAgregarContacto() {
2 let modal = this.modalCtrl.create(AddContactoModal);
3 modal.present();
4
5 modal.onDidDismiss(data => {
6 if (data) {
7 this.contactos.push(data);
8 }
9 });
10 }

Gist²⁵
La parte nueva, es que ahora la función que opera sobre los datos devueltos deja de estar vacía.
Primero verificamos que se hayan recibido datos. Recordemos que data representa un objeto de tipo
contacto que ha sido enviado por la ventana modal.
Si tenemos un objeto contacto, procedemos a agregar el nuevo contacto a los contactos ya existentes,
los cuales se encuentran en nuestra variable contactos que es de tipo array, por lo que utilizamos
para ello el método push.
Si ahora completamos el formulario y presionamos ENVIAR veremos como el nuevo contacto
aparece en nuestra lista de contactos.

Contacto agregado

Podemos ver incluso los detalles del nuevo contacto agregado.


²⁵https://gist.github.com/vihugarcia/df71a58254437d812de6af49bcbd5a2b
Capítulo 2: Primera Aplicación Completa 65

Detalles del contacto agregado

Por supuesto, los datos existen en memoria, y si refrescamos el navegador volveremos a los tres
contactos que teníamos originalmente.
Vamos a ocuparnos ahora de la funcionalidad para editar un contacto.
Como primer paso, vamos a tener que editar nuestro modelo Contacto.
Debajo del constructos, añadamos el siguiente método estático:

1 static clone(contacto: Contacto) {


2 return new Contacto(contacto.id, contacto.nombre, contacto.direccion,
3 contacto.telefono, contacto.email);
4 }

Lo que buscamos aquí, es obtener una nueva instancia de un objeto contacto, que es una copia de
un contacto determinado. Estamos clonando un contacto. Ya veremos cuál es el propósito de ello.
Al hacer el método estático, vamos a poder convocarlo sin tener necesidad de crear una instancia
de un objeto Contacto.
Ahora si nos dirigimos a la plantilla contactos.html, podemos observar las siguientes líneas de código:

1 <button ion-button color="dark" (click)="editarContacto(contacto)">


2 <ion-icon name="create"></ion-icon>
3 Editar
4 </button>

Cuando hacemos clic sobre el botón editar, se invoca un método editarContacto al que se le pasa
como dato el contacto seleccionado.
Vamos a aprovechar la misma ventana modal que utilizamos para crear un nuevo contacto, y
utilizarla para editar los datos del mismo. Por ello, me parece buena idea cambiar el nombre del
método editarContacto por mostrarEditarContacto.
En el archivo contactos.ts, definamos una variable pública de tipo Contacto:
Capítulo 2: Primera Aplicación Completa 66

1 public contactoOriginal: Contacto;

Esta variable contendrá nuestro contacto antes de las modificaciones. Debemos cambiar el nombre
del método editarContacto por el de mostrarEditarContacto.
La implementación completa del método es la siguiente.
Del libro:

1 mostrarEditarContacto(contacto: Contacto) {
2 let modal = this.modalCtrl.create(AddContactoModal, {contacto});
3 this.contactoOriginal = contacto;
4 modal.present();
5
6 modal.onDidDismiss(data => {
7 if (data) {
8 console.log(this.contactoOriginal);
9 console.log(data);
10 }
11 });
12 }

Gist²⁶
Podemos ver que guarda muchas similitudes con el método mostrarAgregarContacto que vimos
anteriormente. Una diferencia, es que antes de presentar la ventana normal, guardamos en nuestra
variable contactoOriginal una referencia al contacto sin modificar. También, la ventana modal recibe
como parámetro el contacto seleccionado. Cuando la ventana modal se cierre (porque se presionó
el botón ENVIAR) por ahora simplemente mostraremos por la consola el contacto original y el
modificado.
Modifiquemos ahora el archivo add-contacto-modal.ts
Lo que haremos ahora, es cambiar la implementación del constructor. Reemplacémoslo por lo
siguiente:

1 constructor(public navCtrl: NavController, public navParams: NavParams,


2 public viewCtrl: ViewController) {
3 if (this.navParams.get('contacto')) {
4 this.contacto = Contacto.clone(this.navParams.get('contacto'));
5 }
6 }

²⁶https://gist.github.com/vihugarcia/5a9906121767122a12eb3304b1c785ec
Capítulo 2: Primera Aplicación Completa 67

Si la ventana modal recibe como parámetro un contacto, entonces la variable contacto, contendrá
ahora una copia del contacto recibido. Aquí vemos la utilidad del método clone. Queremos una
copia, y no una referencia al propio objeto. Por ello no podemos usar simplemente this.contacto =
contacto.
Veamos el funcionamiento hasta ahora.
Partamos de nuestra pantalla inicial:

Pantalla Inicial

Y seleccionemos Editar para el primer contacto.

Editar contacto

Vemos como ahora la pantalla modal se carga con los datos del contacto seleccionado. Muy bien.
Sin embargo, hay un pequeño detalle. El título muestra Agregar Contacto, lo cual no es correcto.
Capítulo 2: Primera Aplicación Completa 68

Vamos a agregar una nueva línea al constructor de add-contacto-modal.ts

1 constructor(public navCtrl: NavController, public navParams: NavParams,


2 public viewCtrl: ViewController) {
3 if (this.navParams.get('contacto')) {
4 this.contacto = Contacto.clone(this.navParams.get('contacto'));
5 this.titulo = 'Editar Contacto';
6 }
7 }

Se ha resaltado en negrita la nueva línea agregada.


Repitamos ahora el proceso.

Editar contacto

Ahora está mejor.


Modifiquemos algunos datos del contacto.
Si presionamos ENVIAR, veremos lo siguiente en la consola:

Consola

Gracias a ello, sabemos que el formulario está trabajando perfectamente.


Capítulo 2: Primera Aplicación Completa 69

Estupendo. Vamos a quitar ahora los mensajes de la consola, y modificar el método mostrarEditar-
Contacto para que los cambios se vean reflejados en nuestra lista.
Del libro:

1 mostrarEditarContacto(contacto: Contacto) {
2 let modal = this.modalCtrl.create(AddContactoModal, {contacto});
3 this.contactoOriginal = contacto;
4 modal.present();
5
6 modal.onDidDismiss(data => {
7 if (data) {
8 let index = this.contactos.indexOf(this.contactoOriginal);
9 this.contactos = [
10 ...this.contactos.slice(0,index),
11 data,
12 ...this.contactos.slice(index+1)
13 ];
14 }
15 });
16 }

Gist²⁷
Como verán, hemos quitado la impresión por consola. Analicemos los cambios: let index =
this.contactos.indexOf(this.contactoOriginal);
Aquí estamos definiendo una variable local, llamada index, y estamos guardando en ella la posición
que ocupa en nuestro arreglo de contactos el contacto original.
Recordemos que la variable contactos no es otra cosa que un arreglo de objetos de tipo Contacto.
El método indexOf, es un método de búsqueda, que devuelve la posición en el arreglo donde se
encuentra un elemento que pasamos como parámetro. Por ello era importante guardar una referencia
a nuestro contacto original.
Luego tenemos:

1 this.contactos = [
2 ...this.contactos.slice(0,index),
3 data,
4 ...this.contactos.slice(index+1)
5 ];

²⁷https://gist.github.com/vihugarcia/a015ac35a06f2414f6826905b70ebe45
Capítulo 2: Primera Aplicación Completa 70

¿Qué estamos haciendo aquí?


Pues bien, lo queremos hacer, es guardar en la lista de contactos, todos los elementos del arreglo que
se encuentran antes del elemento a modificar.
En la posición donde se encuentra el elemento original, colocamos el elemento modificado que está
disponible en el parámetro data.
Luego, tomamos todos los elementos posteriores al elemento modificado.
El método slice en un arreglo, nos devuelve una porción del arreglo. Realizar un slice de 0 a index,
devuelve una porción del arreglo compuesta por las posiciones 0, 1, etc. Hasta la posición index.
Los puntos suspensivos (…) colocados antes de la porción del arreglo, indican que esta porción debe
expandirse en cada uno de los elementos que la componen.
Es decir, supongamos que index tiene el valor 2.
…this.contactos.slice(0, 2) es equivalente a:
this.contactos(0), this.contactos(1)
Si omitimos el segundo parámetro del método slice, entonces la porción del arreglo que se tomará
es desde la posición indicada hasta el final del arreglo.
Si ahora editamos un contacto, veremos que los cambios se ven reflejados en nuestra lista.

Contacto editado

Podemos modificar todos los contactos si lo deseamos, y funcionará perfectamente.


¡Genial! Nuestra aplicación va tomando forma ya.
Nos resta ocuparnos de la eliminación de un contacto.
En contactos.html tenemos las siguientes líneas:
Capítulo 2: Primera Aplicación Completa 71

1 <button ion-button color="danger" (click)="eliminarContacto(contacto)">


2 <ion-icon name="remove-circle"></ion-icon>
3 Eliminar
4 </button>

Podemos escribir la implementación del método eliminarContacto como sigue.


Del libro:

1 eliminarContacto(contacto: Contacto) {
2 let index = this.contactos.indexOf(contacto);
3 this.contactos = [
4 ...this.contactos.slice(0,index),
5 ...this.contactos.slice(index+1)
6 ];
7 }

Gist²⁸
Estamos usando nuevamente indexOf para obtener la posición del elemento, y slice para obtener
todos los elementos del arreglo con excepción del elemento que deseamos eliminar.
Podemos ver esto en funcionamiento:

Eliminar contacto

²⁸https://gist.github.com/vihugarcia/f8af86a087f0d9909ba2d176dba09723
Capítulo 2: Primera Aplicación Completa 72

Contacto eliminado

Funciona, pero en pos de reducir la posibilidad de errores involuntarios, sería interesante mostrarle
al usuario un mensaje de advertencia y darle la oportunidad de decidir si realmente quiere eliminar
el contacto o no.
Vamos a ver cómo realizar esto, y de paso introduciremos un nuevo componente que seguramente
nos será de utilidad en nuestros proyectos.
Lo que vamos a hacer, es que en el momento en que el usuario presione el botón Eliminar, se muestre
un cuadro de diálogo preguntándole si realmente desea eliminar el contacto, dándole la posibilidad
de aceptar o cancelar la eliminación.
Para ello, tenemos disponible un componente denominado AlertController. Este nos permite
presentar distintos mensajes, tales como cuadros de diálogo y confirmación.
En contactos.ts, debemos modificar la línea:

1 import { NavController, NavParams, ModalController } from 'ionic-angular';

Por:

1 import { NavController, NavParams, ModalController, AlertController }


2 from 'ionic-angular';

El constructor también debe ser modificado para recibir ahora como parámetro una nueva variable
de tipo AlertController para poder utilizarlo.
Cambiemos el constructor a:
Capítulo 2: Primera Aplicación Completa 73

1 constructor(
2 public navCtrl: NavController,
3 public navParams: NavParams,
4 public modalCtrl: ModalController,
5 public alertCtrl: AlertController
6 ) {}

Ahora vamos a crear un nuevo método que será el encargado de presentar al usuario el cuadro de
confirmación, y en caso de que el usuario acepte, se convocará el método que elimina el contacto
que no es otro que eliminarContacto.
Del libro:

1 confirmarEliminarContacto(contacto: Contacto) {
2 let confirm = this.alertCtrl.create({
3 title: 'Eliminar contacto',
4 message: '¿Realmente desea eliminar el contacto?',
5 buttons: [
6 {
7 text: 'Cancelar'
8 },
9 {
10 text: 'Eliminar',
11 handler: () => {
12 this.eliminarContacto(contacto);
13 }
14 }
15 ]
16 });
17 confirm.present();
18 }

Gist²⁹
Finalmente debemos modificar contactos.html para que el presionar el botón Eliminar se convoque
a ese nuevo método.

²⁹https://gist.github.com/vihugarcia/64f40a847e92d5457953d4f43865a75c
Capítulo 2: Primera Aplicación Completa 74

1 <button ion-button color="danger" (click)="confirmarEliminarContacto(contacto)">


2 <ion-icon name="remove-circle"></ion-icon>
3 Eliminar
4 </button>

Si ahora presionamos eliminar, veremos que se nos solicita confirmación:

Diálogo de confirmación

¿No es genial?
En muy poco tiempo, hemos podido construir una aplicación completamente funcional con un
aspecto decente.
Existen por supuesto muchos detalles que podríamos mejorar, pero en este punto espero que haya
podido ver el enorme potencial de esta herramienta.
Las cosas que podemos hacer, están limitadas solamente por aquello que podamos concebir. Antes
de finalizar el capítulo, y simplemente para asegurarnos de que todo funciona correctamente, voy a
proporcionar el listado completo de los archivos.
Página de Contactos
contactos.html
Del libro:
Capítulo 2: Primera Aplicación Completa 75

1 <!--
2 Generated template for the Contactos page.
3
4 See http://ionicframework.com/docs/v2/components/#navigation for more info on
5 Ionic pages and navigation.
6 -->
7 <ion-header>
8
9 <ion-navbar color="primary">
10 <ion-title>{{titulo}}</ion-title>
11 </ion-navbar>
12
13 </ion-header>
14
15
16 <ion-content padding>
17 <h2>Contactos</h2>
18
19 <p>{{texto}}</p>
20
21 <ion-item>
22 <ion-input type="text" placeholder="escriba algo..."
23 [(ngModel)]="texto"></ion-input>
24 </ion-item>
25
26 <ion-list>
27 <ion-item-sliding *ngFor="let contacto of contactos">
28 <ion-item>
29 <ion-label>{{contacto.nombre}}</ion-label>
30 <ion-label>{{contacto.direccion}}</ion-label>
31 </ion-item>
32
33 <ion-item-options side="right">
34 <button ion-button color="favorite" (click)="verContacto(contacto)">
35 <ion-icon name="eye"></ion-icon>
36 Ver
37 </button>
38 <button ion-button color="dark"
39 (click)="mostrarEditarContacto(contacto)">
40 <ion-icon name="create"></ion-icon>
41 Editar
42 </button>
Capítulo 2: Primera Aplicación Completa 76

43 <button ion-button color="danger"


44 (click)="confirmarEliminarContacto(contacto)">
45 <ion-icon name="remove-circle"></ion-icon>
46 Eliminar
47 </button>
48 </ion-item-options>
49 </ion-item-sliding>
50 </ion-list>
51 </ion-content>
52
53 <ion-fab right bottom>
54 <button ion-fab (click)="mostrarAgregarContacto()">
55 <ion-icon name="add"></ion-icon></button>
56 </ion-fab>

Gist³⁰
contactos.ts
Del libro:

1 import { Component } from '@angular/core';


2 import { NavController, NavParams, ModalController, AlertController }
3 from 'ionic-angular';
4 import { Contacto } from '../../model/contacto';
5 import { ContactoPage } from '../contacto/contacto';
6 import { AddContactoModal } from '../add-contacto-modal/add-contacto-modal';
7
8 /*
9 Generated class for the Contactos page.
10
11 See http://ionicframework.com/docs/v2/components/#navigation for more info on
12 Ionic pages and navigation.
13 */
14 @Component({
15 selector: 'page-contactos',
16 templateUrl: 'contactos.html'
17 })
18 export class ContactosPage {
19 titulo: String = 'Administrador de Contactos';
20 public contactos = [
21 new Contacto(1, "Andrea Gómez", "Calle Uno 123", "12345", "gomez@gmail.com"),

³⁰https://gist.github.com/vihugarcia/8331f50647bde1a3051ce59dbcbd14af
Capítulo 2: Primera Aplicación Completa 77

22 new Contacto(2, "Juan Perez", "Calle Dos 567", "23456", "perez@gmail.com"),


23 new Contacto(3, "Martín Álvarez", "Calle del Pueblo 628", "34567",
24 "alvarez@gmail.com")
25 ];
26 public contactoOriginal: Contacto;
27
28 constructor(
29 public navCtrl: NavController,
30 public navParams: NavParams,
31 public modalCtrl: ModalController,
32 public alertCtrl: AlertController
33 ) {}
34
35 ionViewDidLoad() {
36 console.log('ionViewDidLoad ContactosPage');
37 }
38
39 verContacto(contacto: Contacto) {
40 this.navCtrl.push(ContactoPage, {contacto});
41 }
42
43 mostrarEditarContacto(contacto: Contacto) {
44 let modal = this.modalCtrl.create(AddContactoModal, {contacto});
45 this.contactoOriginal = contacto;
46 modal.present();
47
48 modal.onDidDismiss(data => {
49 if (data) {
50 let index = this.contactos.indexOf(this.contactoOriginal);
51 this.contactos = [
52 ...this.contactos.slice(0,index),
53 data,
54 ...this.contactos.slice(index+1)
55 ];
56 }
57 });
58 }
59
60 confirmarEliminarContacto(contacto: Contacto) {
61 let confirm = this.alertCtrl.create({
62 title: 'Eliminar contacto',
63 message: '¿Realmente desea eliminar el contacto?',
Capítulo 2: Primera Aplicación Completa 78

64 buttons: [
65 {
66 text: 'Cancelar'
67 },
68 {
69 text: 'Eliminar',
70 handler: () => {
71 this.eliminarContacto(contacto);
72 }
73 }
74 ]
75 });
76 confirm.present();
77 }
78
79 eliminarContacto(contacto: Contacto) {
80 let index = this.contactos.indexOf(contacto);
81 this.contactos = [
82 ...this.contactos.slice(0,index),
83 ...this.contactos.slice(index+1)
84 ];
85 }
86
87 mostrarAgregarContacto() {
88 let modal = this.modalCtrl.create(AddContactoModal);
89 modal.present();
90
91 modal.onDidDismiss(data => {
92 if (data) {
93 this.contactos.push(data);
94 }
95 });
96 }
97
98 }

Gist³¹
Página contacto (detalle de un contacto)
contacto.html
Del libro:
³¹https://gist.github.com/vihugarcia/b1c5a8a22c6fc1645f81c89f3b5cbf2e
Capítulo 2: Primera Aplicación Completa 79

1 <!--
2 Generated template for the Contacto page.
3
4 See http://ionicframework.com/docs/v2/components/#navigation for more info on
5 Ionic pages and navigation.
6 -->
7 <ion-header>
8
9 <ion-navbar color="primary">
10 <ion-title>Contacto</ion-title>
11 </ion-navbar>
12
13 </ion-header>
14
15
16 <ion-content padding>
17 <div *ngIf="contacto">
18 <ion-card>
19 <ion-card-content>
20 <ion-card-title>
21 {{contacto.nombre}}
22 </ion-card-title>
23 <p>{{contacto.direccion}}</p>
24 <p>{{contacto.telefono}}</p>
25 <p>{{contacto.email}}</p>
26 </ion-card-content>
27 </ion-card>
28 </div>
29 </ion-content>

Gist³²
contacto.ts
Del libro:

³²https://gist.github.com/vihugarcia/0668d93c567cda8bd8e25766f8959588
Capítulo 2: Primera Aplicación Completa 80

1 import { Component } from '@angular/core';


2 import { NavController, NavParams } from 'ionic-angular';
3
4 import { Contacto } from '../../model/contacto';
5
6 /*
7 Generated class for the Contacto page.
8
9 See http://ionicframework.com/docs/v2/components/#navigation for more info on
10 Ionic pages and navigation.
11 */
12 @Component({
13 selector: 'page-contacto',
14 templateUrl: 'contacto.html'
15 })
16 export class ContactoPage {
17 public contacto: Contacto;
18
19 constructor(public navCtrl: NavController, public navParams: NavParams) {
20 this.contacto = this.navParams.get('contacto');
21 }
22
23 ionViewDidLoad() {
24 console.log('ionViewDidLoad ContactoPage');
25 }
26
27 }

Gist³³
Ventana modal Contacto
add-contacto-modal.html
Del libro:

³³https://gist.github.com/vihugarcia/e2c7812bd8a069fad8383e6ee3e90a30
Capítulo 2: Primera Aplicación Completa 81

1 <!--
2 Generated template for the AddContactoModal page.
3
4 See http://ionicframework.com/docs/v2/components/#navigation for more info on
5 Ionic pages and navigation.
6 -->
7 <ion-header>
8
9 <ion-navbar color="primary">
10 <ion-title>{{titulo}}</ion-title>
11 <ion-buttons start>
12 <button ion-button (click)="dismiss()">
13 <span secondary showWhen="ios">Cancelar</span>
14 <ion-icon name="md-close" showWhen="android, windows">
15 </ion-icon>
16 </button>
17 </ion-buttons>
18 </ion-navbar>
19
20 </ion-header>
21
22
23 <ion-content padding>
24 <form #formContacto="ngForm" class="container" (ngSubmit)="onSubmit()">
25 <ion-item>
26 <ion-input name="nombre" id="nombre" #nombre="ngModel"
27 [(ngModel)]="contacto.nombre" required="required" placeholder="Nombre">
28 </ion-input>
29 </ion-item>
30 <ion-item danger [hidden]="nombre.valid || nombre.untouched">
31 El nombre es obligatorio
32 </ion-item>
33
34 <ion-item>
35 <ion-input name="direccion" id="direccion"
36 #direccion="ngModel" [(ngModel)]="contacto.direccion"
37 required="required" placeholder="Dirección"></ion-input>
38 </ion-item>
39 <ion-item danger [hidden]="direccion.valid || direccion.untouched">
40 La dirección es obligatoria
41 </ion-item>
42
Capítulo 2: Primera Aplicación Completa 82

43 <ion-item>
44 <ion-input name="telefono" id="telefono"
45 #telefono="ngModel" [(ngModel)]="contacto.telefono"
46 required="required" placeholder="Teléfono"></ion-input>
47 </ion-item>
48 <ion-item danger [hidden]="telefono.valid || telefono.untouched">
49 El teléfono es obligatorio
50 </ion-item>
51
52 <ion-item>
53 <ion-input name="email" id="email" #email="ngModel"
54 [(ngModel)]="contacto.email" placeholder="E-Mail"></ion-input>
55 </ion-item>
56
57 <div class="submit-button">
58 <button ion-button block
59 type="submit" [disabled]="!formContacto.form.valid">Enviar</button>
60 </div>
61
62 </form>
63 </ion-content>

Gist³⁴
add-contacto-modal.ts
Del libro:

1 import { Component } from '@angular/core';


2 import { NavController, NavParams, ViewController } from 'ionic-angular';
3 import { Contacto } from '../../model/contacto';
4
5 /*
6 Generated class for the AddContactoModal page.
7
8 See http://ionicframework.com/docs/v2/components/#navigation for more info on
9 Ionic pages and navigation.
10 */
11 @Component({
12 selector: 'page-add-contacto-modal',
13 templateUrl: 'add-contacto-modal.html'
14 })

³⁴https://gist.github.com/vihugarcia/6c8615e9bd2e0f9d9c12366a0221a47c
Capítulo 2: Primera Aplicación Completa 83

15 export class AddContactoModal {


16 public contacto = new Contacto(0, '', '', '', '');
17 public titulo : String = 'Agregar Contacto';
18
19 constructor(public navCtrl: NavController, public navParams: NavParams,
20 public viewCtrl: ViewController) {
21 if (this.navParams.get('contacto')) {
22 this.contacto = Contacto.clone(this.navParams.get('contacto'));
23 this.titulo = 'Editar Contacto';
24 }
25 }
26
27 ionViewDidLoad() {
28 console.log('ionViewDidLoad AddContactoModalPage');
29 }
30
31 onSubmit() {
32 this.viewCtrl.dismiss(this.contacto);
33 }
34
35 }

Gist³⁵
Módulo principal
app.module.ts
Del libro:

1 import { NgModule, ErrorHandler } from '@angular/core';


2 import { IonicApp, IonicModule, IonicErrorHandler } from 'ionic-angular';
3 import { MyApp } from './app.component';
4 import { HomePage } from '../pages/home/home';
5 import { ContactosPage } from '../pages/contactos/contactos';
6 import { ContactoPage } from '../pages/contacto/contacto';
7 import { AddContactoModal }
8 from '../pages/add-contacto-modal/add-contacto-modal';
9
10 @NgModule({
11 declarations: [
12 MyApp,

³⁵https://gist.github.com/vihugarcia/1358d8e082dc07016b845a4e09c646aahttps:/gist.github.com/vihugarcia/1358d8e082dc07016b845a4e09c646aa
Capítulo 2: Primera Aplicación Completa 84

13 HomePage,
14 ContactosPage,
15 ContactoPage,
16 AddContactoModal
17 ],
18 imports: [
19 IonicModule.forRoot(MyApp)
20 ],
21 bootstrap: [IonicApp],
22 entryComponents: [
23 MyApp,
24 HomePage,
25 ContactosPage,
26 ContactoPage,
27 AddContactoModal
28 ],
29 providers: [{provide: ErrorHandler, useClass: IonicErrorHandler}]
30 })
31 export class AppModule {}

Gist³⁶
Componente principal
app.component.ts
Del libro:

1 import { Component } from '@angular/core';


2 import { Platform } from 'ionic-angular';
3 import { StatusBar, Splashscreen } from 'ionic-native';
4
5 import { HomePage } from '../pages/home/home';
6 import { ContactosPage } from '../pages/contactos/contactos';
7
8
9 @Component({
10 templateUrl: 'app.html'
11 })
12 export class MyApp {
13 rootPage = ContactosPage;
14
³⁶https://gist.github.com/vihugarcia/bf8d837a12ca8e016ef88b0abd7bff47
Capítulo 2: Primera Aplicación Completa 85

15 constructor(platform: Platform) {
16 platform.ready().then(() => {
17 // Okay, so the platform is ready and our plugins are available.
18 // Here you can do any higher level native things you might need.
19 StatusBar.styleDefault();
20 Splashscreen.hide();
21 });
22 }
23 }

Gist³⁷
En el próximo capítulo, comenzaremos a trabajar en la configuración de nuestro servidor de
desarrollo, de manera que podamos obtener datos desde una fuente externa a nuestra aplicación.
Veremos cómo comunicarnos de manera asíncrona con un servidor, manejar errores, y muchas cosas
más que llevarán nuestras capacidades a un nivel.
Espero que lo disfruten.
³⁷https://gist.github.com/vihugarcia/6e35a7eb61333329ac3900cdfccc44be
Capítulo 3: Servidor de desarrollo
A partir de aquí vamos a comenzar a trabajar con datos provenientes de un servicio web. Para esto,
es necesario que configuremos completamente nuestro servidor de desarrollo.
Si no lo han hecho anteriormente, deben instalar ahora algún software que permita configurar un
servidor web.
En uno de los apéndices de este libro, se muestra como instalar xampp. Con xampp, podrá tener
configurado Apache y MariaDB (si no ha escuchado de MariaDB, se trata de un motor de base de
datos desarrollado por el mismo creador de MySQL y totalmente compatible con este mismo).
Si están trabajando con Windows, otra alternativa es Wamp. Tiene una funcionalidad muy similar.
Por supuesto, no es obligatorio que utilicen alguna de esas herramientas. Si ya cuentan con un
entorno de desarrollo, entonces no hay razón para que utilicen otro.
Una de las cosas buenas que tiene Xampp, es una interface gráfica para trabajar con bases de datos.
Vamos a crear una base de datos denominada contactmgr y seleccionar para ella como codificación
utf8_unicode_ci.

Creación BD

Vamos a crear ahora una tabla denominada contactos con cinco columnas:

Tabla de contactos

No olviden marcar la columna id como autoincremental.


Capítulo 3: Servidor de desarrollo 87

Una vez creada la tabla, insertemos unos cuantos registros. Podemos utilizar los mismos datos de
nuestro arreglo contactos.

Insertar contactos

La tabla deberá haber quedado similar a:

Registros insertados

Perfecto. Ya tenemos datos para trabajar.


Capítulo 3: Servidor de desarrollo 88

Api Rest
Actualmente es muy común que distintas compañías y organizaciones implementen software en la
forma de un conjunto de servicios.
Un servicio es básicamente un conjunto de funciones que son encapsuladas y están disponibles a
través de una interface.
Los consumidores del servicio (personas u otro software) no necesitan estar al tanto de cómo es
implementado el servicio.
Simplemente necesitan conocer cuál es el protocolo que deben utilizar para comunicarse con el
servicio.
Un tipo de servicios muy común es el de servicio web.

Servicios Web
Un servicio web (en inglés, Web Service o Web services) es una tecnología que utiliza un conjunto de
protocolos y estándares que sirven para intercambiar datos entre aplicaciones. Distintas aplicaciones
de software desarrolladas en lenguajes de programación diferentes, y ejecutadas sobre cualquier
plataforma, pueden utilizar los servicios web para intercambiar datos en redes de ordenadores
como Internet. La interoperabilidad se consigue mediante la adopción de estándares abiertos. Las
organizaciones OASIS y W3C son los comités responsables de la arquitectura y reglamentación de
los servicios Web. Para mejorar la interoperabilidad entre distintas implementaciones de servicios
Web se ha creado el organismo WS-I, encargado de desarrollar diversos perfiles para definir de
manera más exhaustiva estos estándares. Es una máquina que atiende las peticiones de los clientes
web y les envía los recursos solicitados.[1]
Entre los estándares utilizados por los servicios web están los siguientes:

• Web Services Protocol Stack: Así se le denomina al conjunto de servicios y protocolos de los
servicios Web.
• XML (Extensible Markup Language): Es el formato estándar para los datos que se vayan a
intercambiar.
• SOAP (Simple Object Access Protocol) o XML-RPC (XML Remote Procedure Call): Protocolos
sobre los que se establece el intercambio.
• Otros protocolos: los datos en XML también pueden enviarse de una aplicación a otra mediante
protocolos normales como HTTP (Hypertext Transfer Protocol), FTP (File Transfer Protocol),
o SMTP (Simple Mail Transfer Protocol).
• WSDL (Web Services Description Language): Es el lenguaje de la interfaz pública para los
servicios Web. Es una descripción basada en XML de los requisitos funcionales necesarios
para establecer una comunicación con los servicios Web.
Capítulo 3: Servidor de desarrollo 89

• UDDI (Universal Description, Discovery and Integration): Protocolo para publicar la informa-
ción de los servicios Web. Permite comprobar qué servicios web están disponibles.
• WS-Security (Web Service Security): Protocolo de seguridad aceptado como estándar por
OASIS (Organization for the Advancement of Structured Information Standards). Garantiza
la autenticación de los actores y la confidencialidad de los mensajes enviados.
• REST (Representational State Transfer): arquitectura que, haciendo uso del protocolo HTTP,
proporciona una API que utiliza cada uno de sus métodos (GET, POST, PUT, DELETE, etc)
para poder realizar diferentes operaciones entre la aplicación que ofrece el servicio web y el
cliente.

De todas ellas, probablemente es la arquitectura REST aquella que goza de mayor popularidad, y es
la que vamos a utilizar para implementar nuestros servicios.
Ahora bien. Explicar la forma de desarrollar los servicios web escapa del alcance de este libro. Voy a
proporcionarles un enlace para descargar el código fuente que deberían copiar dentro del directorio
raíz de su servidor de desarrollo. Esta raíz puede ser htdocs o www, dependiendo de qué entorno de
desarrollo esté utilizando.
El enlace para descargar el código es el siguiente:
https://drive.google.com/file/d/0B0zIX5vLhmRGTUI5b3l5eWRyLUE/view?usp=sharing³⁸
Otra cosa que recomiendo es instalar la extensión Postman de Google Chrome. Esta extensión
permite probar distintos tipos de peticiones y es excelente para probar apis. Podemos simular
peticiones con distintos parámetros, headers y otras muchas opciones.
No es algo obligatorio sin embargo.
Al descomprimir el archivo descargado, tendrán una carpeta llamada api-cmgr. Recuerden que esa
carpeta debe ir dentro de la raíz de su servidor de desarrollo.
Dentro de la carpeta api-cmgr podrán encontrar un archivo .sql que pueden importar con phpM-
yAdmin para crear la base de datos y la tabla, si es que no lo han hecho anteriormente.
Finalmente, encontrarán un archivo denominado contactos-api.php. Este archivo contiene la api
para administrar recursos de tipo contacto.
No debemos preocuparnos por cómo está construido este archivo, basta con conocer la estructura
de los datos que envía y recibe.
Si les interesa explorar el archivo sin embargo, verán que es muy sencillo. Utiliza un micro
framework llamado Slim³⁹. Es un framework muy simple pero aún así es poderoso y adecuado tanto
para el desarrollo de aplicaciones web como en este caso para APIs.
En el archivo contactos-api.php hay una línea que debemos editar para poner los datos correspon-
dientes a nuestra configuración del servidor.
³⁸https://drive.google.com/file/d/0B0zIX5vLhmRGTUI5b3l5eWRyLUE/view?usp=sharing
³⁹https://www.slimframework.com/
Capítulo 3: Servidor de desarrollo 90

$db = new mysqli(“localhost”, “root”, “”, “contactmgr”);


Eso es todo. No deberíamos tener que modificar nada más.
Si abrimos ahora una nueva pestaña de nuestro navegador y vamos a la dirección: http://localhost/api-
cmgr/contactos-api.php/contactos⁴⁰
Veremos una salida como la siguiente:

Datos de contactos

Si están usando Postman la salida será similar a esta:

Postman

Nuevamente, no es obligatorio instalar esta extensión en el navegador. Es simplemente que se trata


de una herramienta tan buena que no puedo dejar de recomendarla.
Tenemos todo funcionando apropiadamente. Genial. Ahora podemos comenzar a modificar nuestra
aplicación para que consuma recursos desde la fuente de datos que hemos configurado.
Como primera medida, creemos un directorio denominado shared a la misma altura que el directorio
pages, y dentro de este directorio añadamos un archivo llamado app.settings.ts con el siguiente
contenido:

⁴⁰http://localhost/api-cmgr/contactos-api.php/contactos
Capítulo 3: Servidor de desarrollo 91

1 export class AppSettings {


2 public static get API_ENDPOINT() {
3 return 'http://localhost/api-cmgr/contactos-api.php';
4 }
5 }

En este archivo tenemos una función estática que nos devolverá la dirección de nuestro recurso. De
esta forma no tendremos que hard codear dicha ruta en las peticiones que realicemos.
Continuemos.
Ahora creemos un nuevo directorio, a la misma altura que el anterior, denominado services, y dentro
de él creemos un archivo denominado contacto.service.ts.
Comencemos agregando los siguientes imports al archivo:

1 import {Injectable} from "@angular/core";


2 import {Http, Response, Headers} from "@angular/http";
3 import "rxjs/add/operator/map";
4 import {Observable} from "rxjs/Observable";
5 import {Contacto} from "../model/contacto";
6 import {AppSettings} from "../shared/app.settings";

Muy bien. En IONIC, podemos considerar un servicio como un componente que nos permite realizar
peticiones asíncronas a un recurso, y manejar las respuestas que recibimos como resultado de esa
petición, incluyendo situaciones de error.
Los cuatro primeros import lidian con dichos aspectos, en tanto que los dos últimos hacen referencia
a nuestro modelo de datos y a al archivo que contiene la dirección de la API.
A continuación añadamos el siguiente contenido:

1 @Injectable()
2 export class ContactoService {
3 constructor(private _http:Http) {
4
5 }
6
7 getContactos() {
8 return this._http.get(`${AppSettings.API_ENDPOINT}/contactos`)
9 .map(res => res.json());
10 }
11 }
Capítulo 3: Servidor de desarrollo 92

Vemos:
@Injectable() indica que la clase que estamos definiendo podrá ser “inyectada” como dependencia
en otras clases. No nos preocupemos por ello ahora.
A continuación realizamos el export de la clase, para que esta pueda ser utilizada, y finalmente
agregamos un método que tendrá como finalidad devolvernos nuestra lista de contactos.
Noten que en el método getContactos tenemos la siguiente cadena de texto:
${AppSettings.API_ENDPOINT}/contactos

Estamos usando unas comillas especiales, las comillas invertidas (o backticks). Estas comillas son
muy útiles, porque nos permiten expandir variables dentro de ellas. En este caso. Para ello se utiliza
la notación:
${variable}
Dentro de las comillas.
En este caso, entre llaves estamos llamando al método estático API_ENDPOINT de la clase
AppSettings, el cuál como hemos visto devuelve una cadena con la dirección del recurso a consumir.
Eso es todo por ahora en este archivo.
Debemos ahora realizar varias modificaciones a nuestro archivo contactos.ts
En primer lugar, debemos cambiar el primer import por lo siguiente:

1 import { Component, OnInit } from '@angular/core';

Luego, necesitamos agregar el import del servicio que hemos creado.

1 import { ContactoService } from '../../services/contacto.service';

Cambiemos el export de la clase por:

1 export class ContactosPage implements OnInit

Definamos las siguientes dos variables:

1 public status: String;


2 public errorMessage: String;

Modifiquemos el constructor de la siguiente forma:


Capítulo 3: Servidor de desarrollo 93

1 constructor(
2 public navCtrl: NavController,
3 public navParams: NavParams,
4 public modalCtrl: ModalController,
5 public alertCtrl: AlertController,
6 private _contactoService: ContactoService
7 ) {}

Agreguemos dos nuevos métodos.


Del libro:

1 ngOnInit() {
2 this.getContactos();
3 // console.log("contactos-list component cargado");
4 // this.getContactos();
5 }
6
7 getContactos() {
8 this._contactoService.getContactos()
9 .subscribe(
10 result => {
11 this.contactos = result.data;
12 this.status = result.status;
13
14 if (this.status != "success") {
15 console.log("Error en el servidor");
16 }
17 },
18 error => {
19 this.errorMessage = <any>Error;
20 if (this.errorMessage != null) {
21 console.log(this.errorMessage);
22 }
23 }
24 );
25 }

Gist⁴¹
Ahora centremos nuestra atención en el archivo app.module.ts.
Debemos agregar allí un nuevo import del servicio que hemos creado.
⁴¹https://gist.github.com/vihugarcia/8039852af4da181178f884ad931bd582
Capítulo 3: Servidor de desarrollo 94

1 import { ContactoService } from '../services/contacto.service';

Luego necesitamos reemplazar la sección providers por la siguiente:

1 providers: [
2 {provide: ErrorHandler, useClass: IonicErrorHandler},
3 ContactoService
4 ]

Nótese que en este caso no hemos agregado la clase ContactoService a las secciones declarations y
entryComponents.
Esto es así porque ContactoService no es un componente de visualización, sino un servicio inyectable
que debe ser declarado como proveedor.
Si ahora ejecutamos ionic serve, veremos que en apariencia nuestra aplicación no ha sufrido ningún
cambio. Incluso el agregado, edición y eliminación de contactos funciona.

Recursos del servidor

Sin embargo, si seleccionamos la pestaña Network, notaremos lo siguiente:


Capítulo 3: Servidor de desarrollo 95

Petición al servidor

Existe una petición a un recurso externo, que no es otra cosa que nuestra api.
Vemos que esta devuelve una respuesta en formato JSON (json es un formato para intercambiar
datos entre un cliente y un servidor).
Esta respuesta en formato json es convertida en un conjunto de objetos, que son asignados a nuestro
arreglo contactos.
El método encargado de enviar la petición y manipular la respuesta es getContactos, por lo que
procederemos a analizarlo con cuidado.
La respuesta en formato json es la siguiente:
Capítulo 3: Servidor de desarrollo 96

1 {"status":"success",
2 "data":[{"id":"3","nombre":"Mart\u00edn \u00c1lvarez",
3 "direccion":"Calle del Pueblo 628","telefono":"34567",
4 "email":"alvarez@gmail.com"},
5 {"id":"2","nombre":"Juan Perez",
6 "direccion":"Calle Dos 567","telefono":"23456","email":"perez@gmail.com"},
7 {"id":"1","nombre":"Andrea G\u00f3mez",
8 "direccion":"Calle Uno 123","telefono":"12345","email":"gomez@gmail.com"}]}

Tenemos por una lado un elemento denominado status. Este elemento contendrá success o error
dependiendo de si la petición ha sido exitosa o no.
Luego tenemos un elemento data, que está formado por un conjunto de elementos, que en este caso
son los contactos.
Resumiendo: status contiene el estado de la petición, y data contiene la carga útil de datos de la
respuesta.
Vamos ahora al método en cuestión. Voy a mostrarlo primero omitiendo ciertas partes:

1 this._contactoService.getContactos()
2 .subscribe(
3 result => {
4 …
5 },
6 error => {
7 …
8 }
9 }
10 );

La variable privada _contactoService contiene un objeto de tipo ContactoService. A partir de ese


objeto, se invoca el método getContactos que definimos anteriormente en el servicio.
Cuando se invoca a un servicio, uno puede suscribirse a dicho servicio. Es decir, podemos solicitar
que se nos envíe una notificación cuando el servicio haya devuelto una respuesta. Esto es necesario
porque las peticiones son asíncronas y el tiempo de respuesta es variable.
El método suscribe recibe como parámetros dos funciones. La primera corresponde a una respuesta
positiva (una respuesta con código 200 por parte del servicio web) y la otra a una situación de error.
Nótese que se está utilizando aquí la notación de función abreviada en ambos casos.
La primera función recibe una respuesta en el parámetro result y la segunda en el parámetro error.
Luego cada función opera sobre dicho parámetro de manera apropiada.
La función de respuesta positiva es:
Capítulo 3: Servidor de desarrollo 97

1 result => {
2 this.contactos = result.data;
3 this.status = result.status;
4
5 if (this.status != "success") {
6 console.log("Error en el servidor");
7 }
8 }

Lo que hacemos es tomar la carga útil de datos que se encuentra en la respuesta y asignarla a nuestro
arreglo de contactos.
Asimismo, tomamos el estado de la petición y preguntamos si la respuesta ha sido exitosa o no.
Aquí hay que prestar atención y no confundir dos cosas diferentes.
Por un lado, habíamos dicho que esta función se ejecuta en el caso de una respuesta positiva del
servidor. Pero eso se refiere simplemente a una respuesta con estado 200, que significa que el recurso
existía y fue alcanzado y que este respondió.
Por ejemplo, supongamos que realizamos una petición a http://localhost/api-cmgr/contactos-api.php/recurso-
no-existente.
Esta dirección no existe. No hay ningún recurso disponible escuchando en ella. Por lo tanto, en este
caso la respuesta del servidor hubiera sido un código 404 (u otro similar) en lugar de un código 200
y la primera función no se hubiera ejecutado.
Ahora bien. Aunque el recurso se encuentre, es necesaria la segunda verificación

1 if (this.status != "success") {
2 console.log("Error en el servidor");
3 }

Que en este caso se refiere específicamente a la respuesta de la aplicación. Por ejemplo, la api podría
devolver un código de error en status en el caso de que no hubiera contactos.
En el caso de que el servidor responda con un código distinto a 200, se ejecutará la segunda función

1 error => {
2 this.errorMessage = <any>Error;
3 if (this.errorMessage != null) {
4 console.log(this.errorMessage);
5 }
6 }
Capítulo 3: Servidor de desarrollo 98

Que simplemente realiza un log del error.


Vamos a agregar ahora a nuestro servicio un método para añadir nuevos contactos. En contac-
to.service.ts.
Del libro:

1 addContacto(contacto: Contacto) {
2 let json = JSON.stringify(contacto);
3 let params = "json="+json;
4 let headers =
5 new Headers({"Content-Type":"application/x-www-form-urlencoded"});
6
7 return
8 this._http.post(`${AppSettings.API_ENDPOINT}/contactos`,
9 params, {headers: headers})
10 .map(res => res.json());
11 }

Gist⁴²
Agreguemos también un nuevo método para editar contactos, por ahora vacío.

1 editContacto(contacto: Contacto) {
2
3 }

Vamos ahora a trabajar en nuestra ventana modal. Realizando las modificaciones necesarias para
agregar un nuevo contacto en la base de datos.
Añadamos el import de nuestro servicio:

1 import { ContactoService} from '../../services/contacto.service';

Agreguemos una variable pública denominada acción, otras dos variables para el estado de la
petición y mensajes de error, y una variable para contener el id del contacto:

⁴²https://gist.github.com/vihugarcia/be7c74711105e571eb54fc2065ac178a
Capítulo 3: Servidor de desarrollo 99

1 public accion : String;


2 public status: String;
3 public errorMessage: String;
4 public id;

Reformulemos el constructor como sigue:

1 constructor(
2 public navCtrl: NavController,
3 public navParams: NavParams,
4 public viewCtrl: ViewController,
5 private _contactoService: ContactoService)
6 {
7 if (this.navParams.get('contacto')) {
8 this.contacto = Contacto.clone(this.navParams.get('contacto'));
9 this.titulo = 'Editar Contacto';
10 this.accion = 'editar';
11 }
12 }

Reemplacemos la anterior implementación del método onSubmit por esta.


Del libro:

1 onSubmit() {
2 let observable;
3 if (this.accion == "editar") {
4 observable = this._contactoService.editContacto(this.id, this.contacto);
5 } else {
6 observable = this._contactoService.addContacto(this.contacto);
7 }
8 observable.subscribe(
9 response => {
10 this.status = response.status;
11 if (this.status != "success") {
12 console.log("Error en el servidor");
13 }
14 },
15 error => {
16 this.errorMessage = <any>Error;
17 if (this.errorMessage != null) {
18 console.log(this.errorMessage);
Capítulo 3: Servidor de desarrollo 100

19 }
20 }
21 );
22
23 this.viewCtrl.dismiss(this.contacto);
24 }

Gist⁴³
De regreso ahora a contactos.ts
En el método mostrarAgregarContacto reemplacemos la línea:

1 this.contactos.push(data);

Por:

1 this.getContactos();

Con esas modificaciones el agregado de un nuevo contacto debería funcionar. Verifiquemoslo.


Completamos el formulario y vemos aparecer en nuestra lista el nuevo contacto agregado:

Contacto agregado

La diferencia con lo que ocurría anteriormente es que ahora el contacto es agregado en la tabla de
la base de datos.
⁴³https://gist.github.com/vihugarcia/f8bde975ae6edd20c869e1926074cdd7
Capítulo 3: Servidor de desarrollo 101

Registros insertados

Vamos a trabajar ahora en la edición de un contacto.


Editemos el servicio contacto.service.ts para completar la implementación del archivo editContacto.
Debe quedar así.
Del libro:

1 editContacto(id, contacto: Contacto) {


2 let json = JSON.stringify(contacto);
3 let params = "json="+json;
4 let headers = new Headers({"Content-Type":"application/x-www-form-urlencoded"});
5
6 return
7 this._http.put(`${AppSettings.API_ENDPOINT}/update-contacto/`+id,
8 params, {headers: headers})
9 .map(res => res.json());
10 }

Gist⁴⁴
Como pueden ver, el método editContacto es muy similar al método addContacto. Sin embargo hay
una diferencia importante.
En lugar de enviarse una petición mediante un método POST se utiliza una petición de tipo PUT.
En internet hay muchas fuentes valiosas donde se habla del protocolo REST.
Ya que la API que se encuentra en el backend en este caso utiliza el framework Slim, me permite
indicarles un tutorial muy valioso sobre implementación de una api rest con Slim que pueden
encontrar aquí⁴⁵, en caso de que quieran explorar el desarrollo backend.
Esta es una convención del protocolo REST. POST se utiliza para la creación de un nuevo recurso
(un nuevo contacto), y PUT para la modificación de un recurso existente.
Ahora vayamos al archivo add-contacto-modal.ts. Tenemos que agregar una línea en el constructor:
⁴⁴https://gist.github.com/vihugarcia/9b2f38d44be9a7960b06f4c2bbbe8e5e
⁴⁵https://manuais.iessanclemente.net/index.php/Introduccion_a_API_REST_y_framework_Slim_de_PHP#PUT_.28Actualizar.29
Capítulo 3: Servidor de desarrollo 102

1 this.id = this.contacto.id;

A continuación cambiemos la implementación del método mostrarEditarContacto de contactos.ts


de la siguiente manera.
Del libro:

1 mostrarEditarContacto(contacto: Contacto) {
2 let modal = this.modalCtrl.create(AddContactoModal, {contacto});
3 this.contactoOriginal = contacto;
4 modal.present();
5
6 modal.onDidDismiss(data => {
7 if (data) {
8 this.getContactos();
9 }
10 });
11 }

Gist⁴⁶
Con estos cambios ya tenemos funcionando la edición de un contacto. Probemoslo.

Editar contacto

⁴⁶https://gist.github.com/vihugarcia/b4c0da414e482859f1789b395fba1f5d
Capítulo 3: Servidor de desarrollo 103

Editar contacto

¡Genial!
Sólo falta implementar la eliminación de contactos en el servidor.
Vamos a agregar el siguiente método a nuestro servicio.
Del libro:

1 deleteContacto(id) {
2 return this._http.delete(`${AppSettings.API_ENDPOINT}/delete-contacto/`+id)
3 .map(res => res.json());
4 }

Gist⁴⁷
Aquí también quiero señalar algo importante. Como vemos en este caso el tipo de petición que se
envía no es ni POST ni PUT, sino DELETE. Nuevamente estamos siguiendo la convención REST.
Ahora en contactos.ts modifiquemos la implementación del método eliminarContacto de la siguiente
manera.
Del libro:

⁴⁷https://gist.github.com/vihugarcia/9292304084ac03f1537ac84a2f7fe850
Capítulo 3: Servidor de desarrollo 104

1 eliminarContacto(contacto: Contacto) {
2 this._contactoService.deleteContacto(contacto.id)
3 .subscribe(
4 result => {
5 this.status = result.status;
6
7 if (this.status != "success") {
8 console.log("Error en el servidor");
9 }
10 this.getContactos();
11 },
12 error => {
13 this.errorMessage = <any>Error;
14 if (this.errorMessage != null) {
15 console.log(this.errorMessage);
16 }
17 }
18 );
19 }

Gist⁴⁸
Con estos cambios ya debería funcionar la eliminación. Verifiquemoslo.

Eliminar contacto

⁴⁸https://gist.github.com/vihugarcia/dac79aa477a88361046936ade8e4278e
Capítulo 3: Servidor de desarrollo 105

Diálogo de confirmación

Eliminar contacto

¡Fantástico! Nuestra aplicación ya es totalmente funcional. Sin embargo, creo que aún podemos
mejorar la experiencia del usuario.
Pensemos en la siguiente situación.
Supongamos que una petición que estamos realizando a un recurso, demora un tiempo en comple-
tarse.
Durante ese tiempo, el usuario se encuentra con una pantalla que está inactiva.
Capítulo 3: Servidor de desarrollo 106

Puede ser que se impaciente o se confunda, no sabiendo si ocurrió algo malo y si debe ejecutar la
acción nuevamente.
Es siempre conveniente darle alguna clase de feedback al usuario, aún cuando se trate de algo tan
simple como mostrar un indicador de espera.
Afortunadamente, IONIC posee un componente que nos permite mostrar precisamente esto. Este
componente recibe el nombre de LoaderController.
Vamos a ver como implementarlo.
En el archivo contactos.ts modifiquemos el import:

1 import { NavController, NavParams, ModalController, AlertController }


2 from 'ionic-angular';

Por:

1 import { NavController, NavParams, ModalController, AlertController,


2 LoadingController } from 'ionic-angular';

Estamos agregando el componente mencionado. Como ya debemos saber en este punto, tenemos
que modificar el constructor de la clase para recibir un nuevo parámetro cuyo tipo corresponde al
componente importado.
La nueva implementación de nuestro constructor es como sigue:

1 constructor(
2 public navCtrl: NavController,
3 public navParams: NavParams,
4 public modalCtrl: ModalController,
5 public alertCtrl: AlertController,
6 public loadingCtrl: LoadingController,
7 private _contactoService: ContactoService
8 ) {}

Perfecto. Vamos a poner ahora el loader en acción. Supongamos que queremos mostrar un indicador
de espera mientras esperamos que la eliminación de un contacto concluya.
Modifiquemos el método eliminarContacto de la siguiente manera.
Del libro:
Capítulo 3: Servidor de desarrollo 107

1 eliminarContacto(contacto: Contacto) {
2 let loader = this.loadingCtrl.create();
3 loader.present();
4
5 this._contactoService.deleteContacto(contacto.id)
6 .subscribe(
7 result => {
8 this.status = result.status;
9
10 if (this.status != "success") {
11 console.log("Error en el servidor");
12 }
13 this.getContactos();
14 loader.dismiss();
15 },
16 error => {
17 this.errorMessage = <any>Error;
18 if (this.errorMessage != null) {
19 console.log(this.errorMessage);
20 }
21 loader.dismiss();
22 }
23 );
24 }

Gist⁴⁹
Para que podamos apreciar como funciona esto, voy a introducir un retraso intencional en la
respuesta del servidor.
Si ahora seleccionamos un contacto y hacemos clic en eliminar, veremos un loader mientras se
aguarda que la petición concluya.
⁴⁹https://gist.github.com/vihugarcia/23f80ceb3afd550aaaa3c592daac4467
Capítulo 3: Servidor de desarrollo 108

Loader

Este simple detalle mejora mucho la usabilidad de nuestro proyecto.


Con esto hemos concluido las operaciones básicas sobre contactos y tenemos ya una aplicación
funcional.
El próximo capítulo girará en torno a agregar nueva funcionalidad (búsqueda de contacto, ordena-
miento, etc.) y al mismo tiempo mejorar la estética general.
Antes de avanzar, y para asegurarnos de que todo funcione correctamente, voy a dejar el contenido
completo de cada archivo relevante.
Si está atascado con algo, puede comparar el contenido de su archivo con el listado correspondiente
y de esta manera resolver los errores que se puedan presentar.
Página contactos
contactos.html
Del libro:

1 <!--
2 Generated template for the Contactos page.
3
4 See http://ionicframework.com/docs/v2/components/#navigation for more info on
5 Ionic pages and navigation.
6 -->
7 <ion-header>
8
9 <ion-navbar color="primary">
Capítulo 3: Servidor de desarrollo 109

10 <ion-title>{{titulo}}</ion-title>
11 </ion-navbar>
12
13 </ion-header>
14
15
16 <ion-content padding>
17 <h2>Contactos</h2>
18
19 <p>{{texto}}</p>
20
21 <ion-item>
22 <ion-input type="text" placeholder="escriba algo..."
23 [(ngModel)]="texto"></ion-input>
24 </ion-item>
25
26 <ion-list>
27 <ion-item-sliding *ngFor="let contacto of contactos">
28 <ion-item>
29 <ion-label>{{contacto.nombre}}</ion-label>
30 <ion-label>{{contacto.direccion}}</ion-label>
31 </ion-item>
32
33 <ion-item-options side="right">
34 <button ion-button color="favorite" (click)="verContacto(contacto)">
35 <ion-icon name="eye"></ion-icon>
36 Ver
37 </button>
38 <button ion-button color="dark" (click)="mostrarEditarContacto(contacto)">
39 <ion-icon name="create"></ion-icon>
40 Editar
41 </button>
42 <button ion-button color="danger"
43 (click)="confirmarEliminarContacto(contacto)">
44 <ion-icon name="remove-circle"></ion-icon>
45 Eliminar
46 </button>
47 </ion-item-options>
48 </ion-item-sliding>
49 </ion-list>
50 </ion-content>
51
Capítulo 3: Servidor de desarrollo 110

52 <ion-fab right bottom>


53 <button ion-fab (click)="mostrarAgregarContacto()">
54 <ion-icon name="add"></ion-icon></button>
55 </ion-fab>

Gist⁵⁰
contactos.ts
Del libro:

1 import { Component, OnInit } from '@angular/core';


2 import { NavController, NavParams, ModalController, AlertController,
3 LoadingController } from 'ionic-angular';
4 import { Contacto } from '../../model/contacto';
5 import { ContactoPage } from '../contacto/contacto';
6 import { AddContactoModal } from '../add-contacto-modal/add-contacto-modal';
7 import { ContactoService } from '../../services/contacto.service';
8
9 /*
10 Generated class for the Contactos page.
11
12 See http://ionicframework.com/docs/v2/components/#navigation for more info on
13 Ionic pages and navigation.
14 */
15 @Component({
16 selector: 'page-contactos',
17 templateUrl: 'contactos.html'
18 })
19 export class ContactosPage implements OnInit {
20 titulo: String = 'Administrador de Contactos';
21 public contactos = [
22 new Contacto(1, "Andrea Gómez", "Calle Uno 123", "12345", "gomez@gmail.com"),
23 new Contacto(2, "Juan Perez", "Calle Dos 567", "23456", "perez@gmail.com"),
24 new Contacto(3, "Martín Álvarez", "Calle del Pueblo 628", "34567",
25 "alvarez@gmail.com")
26 ];
27 public contactoOriginal: Contacto;
28 public status: String;
29 public errorMessage: String;
30
31 constructor(

⁵⁰https://gist.github.com/vihugarcia/5d4681c5fc0cae8b14e30ad86780ffb6
Capítulo 3: Servidor de desarrollo 111

32 public navCtrl: NavController,


33 public navParams: NavParams,
34 public modalCtrl: ModalController,
35 public alertCtrl: AlertController,
36 public loadingCtrl: LoadingController,
37 private _contactoService: ContactoService
38 ) {}
39
40 ngOnInit() {
41 this.getContactos();
42 }
43
44 getContactos() {
45 this._contactoService.getContactos()
46 .subscribe(
47 result => {
48 this.contactos = result.data;
49 this.status = result.status;
50
51 if (this.status != "success") {
52 console.log("Error en el servidor");
53 }
54 },
55 error => {
56 this.errorMessage = <any>Error;
57 if (this.errorMessage != null) {
58 console.log(this.errorMessage);
59 }
60 }
61 );
62 }
63
64 ionViewDidLoad() {
65 console.log('ionViewDidLoad ContactosPage');
66 }
67
68 verContacto(contacto: Contacto) {
69 this.navCtrl.push(ContactoPage, {contacto});
70 }
71
72 mostrarEditarContacto(contacto: Contacto) {
73 let modal = this.modalCtrl.create(AddContactoModal, {contacto});
Capítulo 3: Servidor de desarrollo 112

74 this.contactoOriginal = contacto;
75 modal.present();
76
77 modal.onDidDismiss(data => {
78 if (data) {
79 this.getContactos();
80 }
81 });
82 }
83
84 confirmarEliminarContacto(contacto: Contacto) {
85 let confirm = this.alertCtrl.create({
86 title: 'Eliminar contacto',
87 message: '¿Realmente desea eliminar el contacto?',
88 buttons: [
89 {
90 text: 'Cancelar'
91 },
92 {
93 text: 'Eliminar',
94 handler: () => {
95 this.eliminarContacto(contacto);
96 }
97 }
98 ]
99 });
100 confirm.present();
101 }
102
103 eliminarContacto(contacto: Contacto) {
104 let loader = this.loadingCtrl.create();
105 loader.present();
106
107 this._contactoService.deleteContacto(contacto.id)
108 .subscribe(
109 result => {
110 this.status = result.status;
111
112 if (this.status != "success") {
113 console.log("Error en el servidor");
114 }
115 this.getContactos();
Capítulo 3: Servidor de desarrollo 113

116 loader.dismiss();
117 },
118 error => {
119 this.errorMessage = <any>Error;
120 if (this.errorMessage != null) {
121 console.log(this.errorMessage);
122 }
123 loader.dismiss();
124 }
125 );
126 }
127
128 mostrarAgregarContacto() {
129 let modal = this.modalCtrl.create(AddContactoModal);
130 modal.present();
131
132 modal.onDidDismiss(data => {
133 if (data) {
134 this.getContactos();
135 }
136 });
137 }
138
139 }

Gist⁵¹
Página de detalles de un contacto
contacto.html
Del libro:

1 <!--
2 Generated template for the Contacto page.
3
4 See http://ionicframework.com/docs/v2/components/#navigation for more info on
5 Ionic pages and navigation.
6 -->
7 <ion-header>
8
9 <ion-navbar color="primary">

⁵¹https://gist.github.com/vihugarcia/b7320c840ccb5c60e1d2e803cdd551c3
Capítulo 3: Servidor de desarrollo 114

10 <ion-title>Contacto</ion-title>
11 </ion-navbar>
12
13 </ion-header>
14
15
16 <ion-content padding>
17 <div *ngIf="contacto">
18 <ion-card>
19 <ion-card-content>
20 <ion-card-title>
21 {{contacto.nombre}}
22 </ion-card-title>
23 <p>{{contacto.direccion}}</p>
24 <p>{{contacto.telefono}}</p>
25 <p>{{contacto.email}}</p>
26 </ion-card-content>
27 </ion-card>
28 </div>
29 </ion-content>

Gist⁵²
contacto.ts
Del libro:

1 import { Component } from '@angular/core';


2 import { NavController, NavParams } from 'ionic-angular';
3
4 import { Contacto } from '../../model/contacto';
5
6 /*
7 Generated class for the Contacto page.
8
9 See http://ionicframework.com/docs/v2/components/#navigation for more info on
10 Ionic pages and navigation.
11 */
12 @Component({
13 selector: 'page-contacto',
14 templateUrl: 'contacto.html'
15 })

⁵²https://gist.github.com/vihugarcia/e7f0a994c701ad7bd1baca907ac73608
Capítulo 3: Servidor de desarrollo 115

16 export class ContactoPage {


17 public contacto: Contacto;
18
19 constructor(public navCtrl: NavController, public navParams: NavParams) {
20 this.contacto = this.navParams.get('contacto');
21 }
22
23 ionViewDidLoad() {
24 console.log('ionViewDidLoad ContactoPage');
25 }
26
27 }

Gist⁵³
Ventana modal
Del libro:

1 <!--
2 Generated template for the AddContactoModal page.
3
4 See http://ionicframework.com/docs/v2/components/#navigation for more info on
5 Ionic pages and navigation.
6 -->
7 <ion-header>
8
9 <ion-navbar color="primary">
10 <ion-title>{{titulo}}</ion-title>
11 <ion-buttons start>
12 <button ion-button (click)="dismiss()">
13 <span secondary showWhen="ios">Cancelar</span>
14 <ion-icon name="md-close" showWhen="android, windows"></ion-icon>
15 </button>
16 </ion-buttons>
17 </ion-navbar>
18
19 </ion-header>
20
21
22 <ion-content padding>
23 <form #formContacto="ngForm" class="container" (ngSubmit)="onSubmit()">

⁵³https://gist.github.com/vihugarcia/dcf7ab7bf60af7f907dc55969bdebb03
Capítulo 3: Servidor de desarrollo 116

24 <ion-item>
25 <ion-input name="nombre" id="nombre" #nombre="ngModel"
26 [(ngModel)]="contacto.nombre" required="required"
27 placeholder="Nombre">
28 </ion-input>
29 </ion-item>
30 <ion-item danger [hidden]="nombre.valid || nombre.untouched">
31 El nombre es obligatorio
32 </ion-item>
33
34 <ion-item>
35 <ion-input name="direccion" id="direccion"
36 #direccion="ngModel" [(ngModel)]="contacto.direccion"
37 required="required" placeholder="Dirección"></ion-input>
38 </ion-item>
39 <ion-item danger [hidden]="direccion.valid || direccion.untouched">
40 La dirección es obligatoria
41 </ion-item>
42
43 <ion-item>
44 <ion-input name="telefono" id="telefono"
45 #telefono="ngModel" [(ngModel)]="contacto.telefono"
46 required="required" placeholder="Teléfono"></ion-input>
47 </ion-item>
48 <ion-item danger [hidden]="telefono.valid || telefono.untouched">
49 El teléfono es obligatorio
50 </ion-item>
51
52 <ion-item>
53 <ion-input name="email" id="email" #email="ngModel"
54 [(ngModel)]="contacto.email" placeholder="E-Mail">
55 </ion-input>
56 </ion-item>
57
58 <div class="submit-button">
59 <button ion-button block
60 type="submit" [disabled]="!formContacto.form.valid">
61 Enviar
62 </button>
63 </div>
64
65 </form>
Capítulo 3: Servidor de desarrollo 117

66 </ion-content>

Gist⁵⁴
add-contacto-modal.ts
Del libro:

1 import { Component } from '@angular/core';


2 import { NavController, NavParams, ViewController } from 'ionic-angular';
3 import { Contacto } from '../../model/contacto';
4 import { ContactoService} from '../../services/contacto.service';
5
6 /*
7 Generated class for the AddContactoModal page.
8
9 See http://ionicframework.com/docs/v2/components/#navigation for more info on
10 Ionic pages and navigation.
11 */
12 @Component({
13 selector: 'page-add-contacto-modal',
14 templateUrl: 'add-contacto-modal.html'
15 })
16 export class AddContactoModal {
17 public contacto = new Contacto(0, '', '', '', '');
18 public titulo : String = 'Agregar Contacto';
19 public accion : String;
20 public status: String;
21 public errorMessage: String;
22 public id;
23
24 constructor(
25 public navCtrl: NavController,
26 public navParams: NavParams,
27 public viewCtrl: ViewController,
28 private _contactoService: ContactoService)
29 {
30 if (this.navParams.get('contacto')) {
31 this.contacto = Contacto.clone(this.navParams.get('contacto'));
32 this.titulo = 'Editar Contacto';
33 this.accion = 'editar';
34 this.id = this.contacto.id;

⁵⁴https://gist.github.com/vihugarcia/b0a10a9f10942bb73b494efbb6f033eb
Capítulo 3: Servidor de desarrollo 118

35 }
36 }
37
38 ionViewDidLoad() {
39 console.log('ionViewDidLoad AddContactoModalPage');
40 }
41
42 onSubmit() {
43 let observable;
44 if (this.accion == "editar") {
45 observable =
46 this._contactoService.editContacto(this.id, this.contacto);
47 } else {
48 observable =
49 this._contactoService.addContacto(this.contacto);
50 }
51 observable.subscribe(
52 response => {
53 this.status = response.status;
54 if (this.status != "success") {
55 console.log("Error en el servidor");
56 }
57 },
58 error => {
59 this.errorMessage = <any>Error;
60 if (this.errorMessage != null) {
61 console.log(this.errorMessage);
62 }
63 }
64 );
65
66 this.viewCtrl.dismiss(this.contacto);
67 }
68
69 }

Gist⁵⁵
Modelo contacto
contacto.ts
Del libro:
⁵⁵https://gist.github.com/vihugarcia/821f02189b29fcb21f239e8b8cc7ea9a
Capítulo 3: Servidor de desarrollo 119

1 export class Contacto{


2 constructor(
3 public id: number,
4 public nombre: string,
5 public direccion: string,
6 public telefono: string,
7 public email: string
8 ){}
9
10 static clone(contacto: Contacto) {
11 return new Contacto(contacto.id, contacto.nombre,
12 contacto.direccion, contacto.telefono, contacto.email);
13 }
14 }

Gist⁵⁶
Archivo de configuración de la dirección de la api
Del libro:

1 export class AppSettings {


2 public static get API_ENDPOINT() {
3 return 'http://localhost/api-cmgr/contactos-api.php';
4 }
5 }

Gist⁵⁷
Servicio
contacto.service.ts
Del libro:

⁵⁶https://gist.github.com/vihugarcia/e53821bb37529027c2a219e11b6628b7
⁵⁷https://gist.github.com/vihugarcia/6693e3bbe3e76e6a76bedca91034c299
Capítulo 3: Servidor de desarrollo 120

1 import {Injectable} from "@angular/core";


2 import {Http, Response, Headers} from "@angular/http";
3 import "rxjs/add/operator/map";
4 import {Observable} from "rxjs/Observable";
5 import {Contacto} from "../model/contacto";
6 import {AppSettings} from "../shared/app.settings";
7
8 @Injectable()
9 export class ContactoService {
10 constructor(private _http:Http) {
11
12 }
13
14 getContactos() {
15 return this._http.get(`${AppSettings.API_ENDPOINT}/contactos`)
16 .map(res => res.json());
17 }
18
19 addContacto(contacto: Contacto) {
20 let json = JSON.stringify(contacto);
21 let params = "json="+json;
22 let headers =
23 new Headers({"Content-Type":"application/x-www-form-urlencoded"});
24
25 return this._http.post(
26 `${AppSettings.API_ENDPOINT}/contactos`,
27 params, {headers: headers})
28 .map(res => res.json());
29 }
30
31 editContacto(id, contacto: Contacto) {
32 let json = JSON.stringify(contacto);
33 let params = "json="+json;
34 let headers =
35 new Headers({"Content-Type":"application/x-www-form-urlencoded"});
36
37 return
38 this._http.put(
39 `${AppSettings.API_ENDPOINT}/update-contacto/`+id,
40 params, {headers: headers})
41 .map(res => res.json());
42 }
Capítulo 3: Servidor de desarrollo 121

43
44 deleteContacto(id) {
45 return
46 this._http.delete(
47 `${AppSettings.API_ENDPOINT}/delete-contacto/`+id)
48 .map(res => res.json());
49 }
50 }

Gist⁵⁸
Módulo principal
Del libro:

1 import { NgModule, ErrorHandler } from '@angular/core';


2 import { IonicApp, IonicModule, IonicErrorHandler } from 'ionic-angular';
3 import { MyApp } from './app.component';
4 import { HomePage } from '../pages/home/home';
5 import { ContactosPage } from '../pages/contactos/contactos';
6 import { ContactoPage } from '../pages/contacto/contacto';
7 import { AddContactoModal }
8 from '../pages/add-contacto-modal/add-contacto-modal';
9 import { ContactoService } from '../services/contacto.service';
10
11 @NgModule({
12 declarations: [
13 MyApp,
14 HomePage,
15 ContactosPage,
16 ContactoPage,
17 AddContactoModal
18 ],
19 imports: [
20 IonicModule.forRoot(MyApp)
21 ],
22 bootstrap: [IonicApp],
23 entryComponents: [
24 MyApp,
25 HomePage,
26 ContactosPage,
27 ContactoPage,

⁵⁸https://gist.github.com/vihugarcia/62e1448ddd5c38fafc2fa37c6f58b56f
Capítulo 3: Servidor de desarrollo 122

28 AddContactoModal
29 ],
30 providers: [
31 {provide: ErrorHandler, useClass: IonicErrorHandler},
32 ContactoService
33 ]
34 })
35 export class AppModule {}

Gist⁵⁹
Componente principal de la aplicación
Del libro:

1 import { Component } from '@angular/core';


2 import { Platform } from 'ionic-angular';
3 import { StatusBar, Splashscreen } from 'ionic-native';
4
5 import { HomePage } from '../pages/home/home';
6 import { ContactosPage } from '../pages/contactos/contactos';
7
8
9 @Component({
10 templateUrl: 'app.html'
11 })
12 export class MyApp {
13 rootPage = ContactosPage;
14
15 constructor(platform: Platform) {
16 platform.ready().then(() => {
17 // Okay, so the platform is ready and our plugins are available.
18 // Here you can do any higher level native things you might need.
19 StatusBar.styleDefault();
20 Splashscreen.hide();
21 });
22 }
23 }

Gist⁶⁰
[1] https://es.wikipedia.org/wiki/Servicio_web
⁵⁹https://gist.github.com/vihugarcia/32cf5530d3d7f1c398738f85e419596f
⁶⁰https://gist.github.com/vihugarcia/1b27b634d7088a58e24208929e74799d
Capítulo 4: Búsquedas y filtros
A lo largo de los capítulos anteriores, hemos desarrollado una aplicación totalmente funcional.
Esta aplicación realiza las operaciones CRUD básicas sobre un conjunto de recursos, contactos en
este caso, que son puestos a disposición por medio de un servicio web.
Sin embargo, siempre debemos procurar lograr una interface lo más usable posible, por lo que,
como habíamos mencionado en el capítulo anterior, vamos a agregar nueva funcionalidad y mejorar
cuestiones estéticas que elevarán grandemente el valor de nuestra aplicación.
Una característica que será de enorme utilidad para los usuarios, sobre todo si el número de contactos
es grande, es la posibilidad de buscar y/o filtrar contactos.
Vamos a ver como implementar dicha característica.
En el archivo contactos.html, cuando explicamos el concepto de data binding, ubicamos un cuadro
de texto con las siguientes líneas:

1 <ion-item>
2 <ion-input type="text" placeholder="escriba algo..." [(ngModel)]="texto">
3 </ion-input>
4 </ion-item>

Vamos a reemplazar dichas líneas por la siguiente:

1 <ion-searchbar [(ngModel)]="terminoBusqueda"></ion-searchbar>

Estamos utilizando un nuevo componente, una barra de búsqueda.


Debemos agregar en contactos.ts, la variable que se está enlazando al modelo.

1 public terminoBusqueda: String = '';

Si ejecutamos ionic serve, el resultado será el siguiente:


Capítulo 4: Búsquedas y filtros 124

Barra de búsqueda

Muy bien. Ahora debemos construir la funcionalidad de búsqueda.


Para ello vamos a introducir un nuevo concepto.

Pipes
Las pipes son clases que permiten formatear datos para ser presentados en una interface.
Una pipe toma un dato de entrada, lo transforma según se necesita, y devuelve el dato transformado
para su visualización.
Angular, el framework detrás de IONIC, viene con una serie de pipes predefinidas que son de utilidad.
Veamos algunos ejemplos.
Supongamos que en alguna de las clases que corresponden a nuestras páginas definimos una variable
denominada fechaNac:

1 public fechaNac = new Date(1982, 4, 12);

Y luego en la correspondiente plantilla incluimos:

1 <p>{{fechaNac}}</p>

La salida será similar a esta:


Capítulo 4: Búsquedas y filtros 125

Fecha sin formatear

Podemos utilizar una de las pipes de IONIC, denominada Date, para formatear la salida:

1 <p>{{fechaNac | date}}</p>

Noten la sintaxis. El dato que se quiere formatear, es seguido del operador | y de la pipe. Ahora la
salida es mucho más agradable:

Fecha formateada

Pero las pipes son aún más potentes, ya que pueden recibir parámetros. Por ejemplo:

1 <p>{{fechaNac | date:"dd/MM/yyyy"}}</p>

La salida ahora es:

Pipe con parámetro

Ahora bien. No estamos limitados a las pipes predefinidas. Podemos construir las nuestras, y eso es
precisamente lo que haremos para implementar la función de búsqueda.
Creemos un directorio denominado pipes a la misma altura que el directorio pages.
Dentro de pipes, añadamos un archivo llamado contact-name-pipe.ts con el siguiente contenido.
Del libro:
Capítulo 4: Búsquedas y filtros 126

1 import { PipeTransform, Pipe } from '@angular/core';


2
3 @Pipe({
4 name: 'contactNamePipe'
5 })
6 export class ContactNamePipe implements PipeTransform {
7 transform(contacts: Array<any>, searchTerm: string) : Array<any> {
8 if (contacts == null) {
9 return [];
10 }
11 if (searchTerm == null) {
12 return contacts;
13 }
14 return contacts.filter( contact => {
15 return `${contact.nombre}`.toLowerCase().indexOf(searchTerm) > -1;
16 });
17 }
18 }

Gist⁶¹
Lo primero que tenemos es el import de los componentes necesarios para trabajar con pipes:

1 import { PipeTransform, Pipe } from '@angular/core';

Luego viene la sección:

1 @Pipe({
2 name: 'contactNamePipe'
3 })

Allí estamos definiendo el nombre de la pipe, que es el que se utilizará al momento de transformar
datos. En este caso, para llamar a la pipe utilizaremos:

1 {{dato | contactNamePipe}}

Ya que ese es el nombre que definimos.


Luego viene el export de la clase:

⁶¹https://gist.github.com/vihugarcia/138e12432f94fb0618b674f78ac0bb3d
Capítulo 4: Búsquedas y filtros 127

1 export class ContactNamePipe implements PipeTransform

ContactNamePipe es el nombre de la clase que se deberá usar en los imports.


Como nuestra pipe implementa la interface PipeTransform, debe forzosamente definir un método
transform.
En este caso nuestra pipe actúa sobre un arreglo (nuestra lista de contactos) y recibe como
parámetro una cadena (nuestro término de búsqueda), y devuelve un arreglo (la lista filtrada)
transform(contacts: Array<any>, searchTerm: string) : Array<any>
Si la lista recibida es nula, se devuelve un arreglo vacío:

1 if (contacts == null) {
2 return [];
3 }

Si el término de búsqueda es vacío, se devuelve la lista completa:

1 if (searchTerm == null) {
2 return contacts;
3 }

Si tenemos una lista y un término de búsqueda, entonces debemos aplicar el filtrado.

1 return contacts.filter( contact => {


2 return `${contact.nombre}`.toLowerCase().indexOf(searchTerm) > -1;
3 });

El método filter en un arreglo, es un método muy útil que recibe una función como parámetro, y
devuelve los elementos para los cuales la función evalúe a verdadero.
La función recibe como parámetro cada uno de los elemento del arreglo. Dentro de la función, el
nombre se pasa a minúsulas, y se busca mediante el método indexOf la ocurrencia de la cadena
correspondiente al término de búsqueda.
indexOf, si encuentra la cadena, devolverá la posición a partir de la cual se encuentra dicha cadena
dentro del nombre. En caso de no encontrarse, devuelve -1.
Al preguntar si el valor de indexOf es mayor que -1 entonces, obtendremos verdadero cuando la
cadena se encuentre, y por lo tanto el contacto en cuestión será devuelto como parte del arreglo
filtrado.
La pipe está definida ya, pero aún no podemos utilizarla. Necesitamos declararle en app.module.ts
de lo contrario obtendremos un error.
Primero agregamos el import:
Capítulo 4: Búsquedas y filtros 128

1 import { ContactNamePipe } from '../pipes/contact-name-pipe';

Luego debemos incluir la clase en la sección declarations. No es necesario incluirla en entryCompo-


nents porque no se trata de un elemento de visualización.
Ahora si, podemos ir a contactos.html y modificar la sección donde se itera a través de los elementos:

1 <ion-item-sliding
2 *ngFor="let contacto of (contactos | contactNamePipe:terminoBusqueda)">

Ahora podemos poner en funcionamiento nuestra opción de búsqueda:

Búsqueda

Búsqueda

¡Magnífico! Hemos agregado una pieza de funcionalidad que sin dudas los usuarios apreciarán.
Como un detalle final, si les desagrada el texto que aparece como placeholder (Search) este puede
ser fácilmente modificado de la siguiente manera:
Capítulo 4: Búsquedas y filtros 129

1 <ion-searchbar [(ngModel)]="terminoBusqueda" placeholder="Nombre...">


2 </ion-searchbar>

Con lo que verán:

Placeholder

Gravatar
Vamos a ocuparnos ahora de una cuestión estética. Si recuerdan del comienzo del capítulo 2, cuando
diseñamos la pantalla inicial llegamos a algo así:
Capítulo 4: Búsquedas y filtros 130

Diseño de pantalla

Tenemos una imagen que se muestra para cada contacto. Las imágenes que vamos a mostrar para
cada contacto es un gravatar.
Para los que no están al tanto de lo que es un gravatar, pueden visitar el sitio http://en.gravatar.com/
Para darnos una rápida idea de lo que queremos lograr, vamos a ubicar una placeholder como
imagen.
En la página de detalle de contacto, justo encima del nombre, coloquemos lo siguiente:

1 <img src="http://placehold.it/50x50">

El sitio placehold.it sirve imágenes del tamaño solicitado para que actúen como placeholders.
Si vamos al detalle de un contacto, veremos:
Capítulo 4: Búsquedas y filtros 131

Placeholder

Muy bien. Ahora debemos incorporar a nuestro proyecto el paquete npm crypto-md5.
Desde la raíz de nuestro proyecto ejecutemos:
npm install crypto-md5 –save
El proceso puede demorar un par de minutos.
Ahora podemos usar el nuevo paquete desde cualquier lugar que lo necesitemos con el siguiente
import:
import md5 from ‘crypto-md5’;
Vamos a agregar dicho import en el archivo contacto.ts.
A continuación, definamos una variable que contendrá la imagen.

1 profilePicture: any;

Ahora modifiquemos la definición del constructor de la siguiente manera:


Capítulo 4: Búsquedas y filtros 132

1 constructor(public navCtrl: NavController, public navParams: NavParams) {


2 this.contacto = this.navParams.get('contacto');
3 this.profilePicture =
4 "https://www.gravatar.com/avatar/" +
5 md5(this.contacto.email.toLowerCase(), 'hex');
6 }

En el archivo contacto.html, cambiemos la disposición de la ion-card como sigue:

1 <ion-card>
2 <ion-item>
3 <ion-avatar>
4 <img [src]="profilePicture">
5 </ion-avatar>
6 </ion-item>
7 <ion-card-content>
8 <ion-card-title>
9
10 {{contacto.nombre}}
11 </ion-card-title>
12 <p>{{contacto.direccion}}</p>
13 <p>{{contacto.telefono}}</p>
14 <p>{{contacto.email}}</p>
15 </ion-card-content>
16 </ion-card>

Si ahora vamos a ver un contacto, y en caso de que este tenga un gravatar, veremos algo así:

Gravatar
Capítulo 4: Búsquedas y filtros 133

En caso de que el contacto no tenga un gravatar (al menos asociado a la dirección de email que se
introdujo) se verá una imagen genérica:

Sin gravatar

Para finalizar, vamos a darle un poco de estilo a la imagen.


Editemos el archivo contacto.scss de la siguiente manera:

1 page-contacto {
2 ion-avatar > img {
3 margin: auto;
4 width: 100px !important;
5 height: auto !important;
6 }
7 }

El resultado es el siguiente:
Capítulo 4: Búsquedas y filtros 134

Gravatar con estilo


Capítulo 5: Corriendo la aplicación
desde el emulador
Hasta ahora hemos venido corriendo nuestra aplicación desde el navegador. En este capítulo,
veremos cómo ejecutar nuestra aplicación en un emulador, para poder apreciar cómo se vería en
un ambiente real.
Puntos importantes para señalar:

• Para poder realizar la emulación (y posteriormente para generar la aplicación que será subida
a las app store), debemos tener en cuenta la plataforma de destino.
• Para poder compilar el proyecto para IOS, necesitaremos una MAC. Alternativamente
podremos utilizar algún ambiente de desarrollo en la nube.

Vamos a trabajar con Android. Para esto debemos tener instalado el SDK de Android.
Desde la raíz de nuestro proyecto, ejecutemos el siguiente comando:
ionic run android
Después de unos minutos podremos ver el emulador en acción. Y…la aplicación devuelve un error.
Capítulo 5: Corriendo la aplicación desde el emulador 136

Error emulador

Es un excelente momento para introducir el proceso de depuración de aplicaciones en ejecución.


¿Cómo podemos depurar una aplicación que se está ejecutando en el emulador?
Aquí es donde vemos la ventaja de utilizar Google Chrome.
Si abrimos una nueva pestaña y escribimos en la barra de direcciones: chrome://inspect podremos
ver la siguiente pantalla:
Capítulo 5: Corriendo la aplicación desde el emulador 137

Chrome Inspect

Si hacemos clic en inspect, se abrirá una nueva ventana de herramienta de desarrollador:

Herramientas de desarrollador

Si nos dirigimos a la pestaña Console, podemos apreciar el siguiente error:


Capítulo 5: Corriendo la aplicación desde el emulador 138

Error de consola

Vemos que se ha rechazado la conexión a:


http://localhost/api-cmgr/contactos-api.php/contactos
Y aquí debemos señalar un detalle muy importante.
En este caso, estamos consumiendo datos de un web service que montamos en nuestra propia
máquina. Esto funciona perfectamente cuando corremos la aplicación en el navegador utilizando
ionic serve, pero al correr desde un emulador, este no tiene idea de quién es localhost. Debemos
utilizar una dirección ip que pueda ser encontrada.
Por lo tanto, para poder correr nuestra aplicación desde el emulador debemos hacer un cambio en
app.settings.ts.
Como primera medida, vamos a obtener la dirección ip local de nuestra máquina. Esto dependerá
del sistema operativo que tengamos.
En mi caso es 10.147.64.246
Editemos app.settings.ts de la siguiente manera:

1 export class AppSettings {


2 public static get API_ENDPOINT() {
3 //return 'http://localhost/api-cmgr/contactos-api.php';
4 return 'http://10.147.64.246/api-cmgr/contactos-api.php';
5 }
6 }

Ejecutemos nuevamente ionic run android.


Deberíamos ver la aplicación funcionando:
Capítulo 5: Corriendo la aplicación desde el emulador 139

App corriendo en el emulador


Capítulo 5: Corriendo la aplicación desde el emulador 140

App corriendo en el emulador

Fantástico.

APK
Observemos el contenido del directorio de nuestro proyecto. Como resultado de haber ejecutado el
comando ionic run android, vemos que hay presente un directorio platforms, y dentro de este un
directorio android. Este directorio android contiene una carpeta denominada build, y dentro de ella
hay otra llamada outputs.
Capítulo 5: Corriendo la aplicación desde el emulador 141

Finalmente, dentro de esta carpeta outputs tenemos un directorio llamado apk. Hay dos archivos
con extensión apk dentro de este directorio:
android-debug.apk y android-debug-unaligned.apk
Aunque se trata de archivos .apk, no es ninguno de ellos un archivo apto para poder subirlo a una
playstore. Tenemos que realizar un proceso para poder publicar nuestra aplicación.

Publicando una aplicación para Android


Como primera parte del proceso, vamos a editar el archivo config.xml que se encuentra en la raíz
de nuestro proyecto. La información que se encuentre en este archivo será tenida en cuenta por el
proceso de creación de la versión release, por eso debemos ajustarlo.
En el encontraremos estas líneas:

1 <widget id="com.ionicframework.contactmgrapp628821" version="0.0.1"


2 xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0\
3 ">

Debemos cambiar el valor de id a algo único que esté relacionado con nosotros. Podría ser por
ejemplo algo basado en el nombre de nuestra compañía o nuestro sitio personal.
Por ejemplo: com.misuperempresa.contactmgrapp
El valor de versión puede quedar como está, pero hay que tener en cuenta que cada vez que subamos
una actualización de nuestra aplicación deberemos incrementar dicho valor.
Después tenemos las siguientes líneas:

1 <description>An awesome Ionic/Cordova app.</description>


2 <author email="hi@ionicframework" href="http://ionicframework.com/">
3 Ionic Framework Team</author>

Que también deberemos ajustar.


Muy bien. Hecho esto podemos comenzar a trabajar en la línea de comandos.
Primero ejecutamos desde la raíz de nuestro proyecto:
ionic build android –release
Esto generará una versión release en el directorio outputs. El proceso puede demorar varios minutos.
Cuando concluya verá algo similar a esto:
Capítulo 5: Corriendo la aplicación desde el emulador 142

Build de la aplicación

Y podrán encontrar un archivo denominado android-release-unsigned.apk


Esta es la versión release de nuestra apk, pero como su nombre lo indica, no está firmada aún.
Para firmar una apk, debemos generar una key privada. Esta generación sólo tendremos que
realizarla una vez. Luego podremos usar esa misma key para firmar nuevamente cada actualización
de nuestra app. De esto se deduce que si por alguna razón llegamos a perder el archivo .keystore que
se generará, no seremos ya capaces de enviar actualización a la app store.
El comando para generar la key tiene la forma:
Del libro:

1 keytool -genkey -v -keystore my-release-key.keystore -alias alias_name -keyalg


2 RSA -keysize 2048 -validity 10000

Gist⁶²
Debemos sustituir my-release-key.keystore por el nombre que deseamos tenga nuestro archivo,
por ejemplo contactmgrapp.keystore y alias_name por un nombre que haga referencia a nuestra
aplicación, por ejemplo contactmgrapp.
Se inciará un asistente que nos irá pidiendo datos. En primer lugar una contraseña para el almacén
de claves.
Una vez finalizado el asistente, tendremos el archivo .keystore generado.
⁶²https://gist.github.com/vihugarcia/bec8b2483521886a85e953d26f081fa8
Capítulo 5: Corriendo la aplicación desde el emulador 143

Archivo de claves generados

Ahora debemos firmar nuestra aplicación utilizando el archivo .keystore generado. Para simplificar
el proceso, vamos a copiar el archivo android-release-unsigned.apk del directorio apk al mismo lugar
donde está el archivo .keystore.
Luego ejecutamos:

1 jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1


2 -keystore my-release-key.keystore HelloWorld-release-unsigned.apk
3 alias_name

Necesitamos reemplazar my-release-key.keystore por el nombre de nuestro archivo .keystore, por


ejemplo contactmgrapp.keystore y HelloWorld-release-unsigned.apk por el nombre de nuestro
archivo unsigned, en este caso android-release-unsigned.apk. También debemos cambiar alias_name
por el nombre de nuestra app, en este caso contactmgrapp.
El último paso, es correr la herramienta de zip align, que tiene como función optimizar la aplicación
para que pueda ser subida a las app store.

1 ruta/a/android/sdk/build-tools/version/zipalign -v 4
2 HelloWorld-release-unsigned.apk HelloWorld.apk

Lógicamente debemos reemplazar ruta/a/android/sdk/build-tools/versión/zipalign por la ruta co-


rrecta para nuestra configuración. HelloWorld-release-unsigned.apk por android-release-unsig-
ned.apk y HelloWorld.apk por el nombre de la aplicación. En mi caso el comando queda como:
c:/Android/sdk/build-tools/23.0.2/zipalign –v 4 android-release-unsigned.apk contactmgrapp.apk
Si todo ha ido bien, debemos encontrar ahora un archivo contactmgrapp.apk en nuestro directorio:

apk lista para subir

¡Magnífico! Ya tenemos nuestra app firmada y alineada, lista para ser enviada a las app store.
El proceso para subir nuestra app es sencillo.
Para el caso de la app store de Google, primero debemos visitar la Consola de Desarrolladores de
Google. Si no contamos con una cuenta, deberemos crear una.
El registro no es gratuito, tiene un costo de 25 dólares. El costo del registro en Apple es de 99 dólares.
¡Ya están listos para sacudir el mundo!
Capítulo 5: Corriendo la aplicación desde el emulador 144

Palabras de despedida
Eso es todo por ahora en lo que respecta a este libro. Tal vez no se hayan percatado aún, pero han
adquirido una gran cantidad de conocimientos que les permitirán desarrollar aplicaciones móviles
de gran calidad.
Y remarco el por ahora, porque este libro seguirá creciendo y actualizándose con nuevo material
para que ustedes puedan obtener el máximo provecho.
Espero por sobre todo haber despertado su curiosidad y sus ansias de aprender. No hay límites para
lo que puedan lograr.
¡Hasta pronto!
Capítulo 6: Apéndices
## Instalando xampp en Linux
En primer lugar, debemos dirigirnos a la página de descargas de xampp, donde tendremos que elegir
la versión correcta para nuestro sistema operativo:
[página de descargas de xampp] (https://www.apachefriends.org/es/download.html)
Cuando hablo de la versión correcta para el sistema operativo, me refiero a que debemos saber si el
mismo es de 32 o 64 bits, de lo contrario nos encontraremos con un error que puede resultar difícil
de resolver.
Permítame explicarle a qué me refiero.
Una vez descargada la versión, y siguiendo lo establecido en las [FAQs de xampp para Linux]
(https://www.apachefriends.org/es/faq_linux.html), debemos ejecutar desde la consola en primer
lugar:

chmod

Lo que estamos haciendo aquí es asignar los permisos correctos (755) al archivo ejecutable, de lo
contrario la instalación no podrá llevarse a cabo. Podemos ver que el ejecutable corresponde a la
versión para 64 bits (como podemos ver por la partícula x64 en el nombre).
Luego, debemos iniciar la ejecución del archivo con:

sudo

Deberá introducir la contraseña de administrador, y presionar enter…y aquí es dónde puede


presentarse un problema:
Si observa el siguiente error:

1 ./xampp-linux-x64-5.6.3-0-installer.run: Syntax error: “(” unexpected


Capítulo 6: Apéndices 146

Que como podemos ver no es para nada descriptivo, entonces probablemente sea debido a que está
tratando de instalar la versión de 64 bits sobre un sistema operativo de 32 bits. Esto puede ocurrir si
hemos SUPUESTO erróneamente que nuestra arquitectura es de 64 bits. Para comprobarlo, podemos
escribir:

1 uname -m

uname

Si, como observamos arriba, obtenemos como respuesta i686, entonces eso nos mostrará que hemos
descargado e intentado ejecutar la versión equivocada. En ese caso debemos dirigirnos nuevamente
a la página de descargas y a obtener la versión correcta, luego procederemos nuevamente a asignar
los permisos adecuados:

chmod

A continuación ejecutamos

1 sudo ./xampp-linux-5.6.3-0-installer.run

Si todo ha ido bien aparecerá el asistente de instalación:


Capítulo 6: Apéndices 147

Instalación

Muy bien, después de presionar Next unas cuantas veces, llegaremos al final:

Fin de instalación

Al presionar Finish, aparecerá la interfaz gráfica de xampp:


Capítulo 6: Apéndices 148

Interface gráfica

Desde la pestaña Manage Servers podremos acceder a los Servicios:

Servicios
Capítulo 6: Apéndices 149

Luego de verificar que Apache y MySQL están activos, podrá dirigirse al navegador e ingresar la
dirección localhost/phpmyadmin

PhpMyAdmin

¡Perfecto! ya tenemos un servidor con Apache y MySQL funcionando.

También podría gustarte