Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Desarrollodeappsconionic PDF
Desarrollodeappsconionic PDF
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.
• 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
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.
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.
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:
¿Por qué ocurre esto? Para comprenderlo abramos nuestro proyecto en nuestro editor de texto. La
estructura es la siguiente:
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 }
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
Pestaña Network
Directorio src
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;
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>
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
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.
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í:
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:
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:
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:
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
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:
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>
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
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
Errores
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:
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
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 <ion-title>{{titulo}}</ion-title>
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.
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 },
Primera pantalla
Como primera medida, agregaremos una variable para contener el título que mostraremos en la
página:
Luego de export class ContactosPage { agregamos:
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
19 HomePage,
20 ContactosPage
21 ],
22 providers: [{provide: ErrorHandler, useClass: IonicErrorHandler}]
23 })
24 export class AppModule {}
Gist¹³
Ahora lancemos ionic serve.
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:
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.
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
Gist¹⁶
Sin embargo, ahora nos encontraremos con que la línea de comandos nos muestra mensajes de error:
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 ];
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>
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
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
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
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:
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 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
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.
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"
²⁴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>
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
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 }
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:
Por:
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
1 mostrarAgregarContacto() {
2 let modal = this.modalCtrl.create(AddContactoModal);
3 modal.present();
4
5 modal.onDidDismiss(data => {});
6 }
1 import { AddContactoModal }
2 from '../pages/add-contacto-modal/add-contacto-modal';
Ventana modal
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>
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
• 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:
Por:
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 #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
Contacto en consola
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
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:
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:
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
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:
²⁶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
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
Editar contacto
Consola
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
Contacto editado
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:
Por:
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
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
Gist³⁰
contactos.ts
Del libro:
³⁰https://gist.github.com/vihugarcia/8331f50647bde1a3051ce59dbcbd14af
Capítulo 2: Primera Aplicación Completa 77
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
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:
³⁴https://gist.github.com/vihugarcia/6c8615e9bd2e0f9d9c12366a0221a47c
Capítulo 2: Primera Aplicación Completa 83
Gist³⁵
Módulo principal
app.module.ts
Del libro:
³⁵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:
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
Una vez creada la tabla, insertemos unos cuantos registros. Podemos utilizar los mismos datos de
nuestro arreglo contactos.
Insertar contactos
Registros insertados
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
Datos de contactos
Postman
⁴⁰http://localhost/api-cmgr/contactos-api.php/contactos
Capítulo 3: Servidor de desarrollo 91
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:
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 constructor(
2 public navCtrl: NavController,
3 public navParams: NavParams,
4 public modalCtrl: ModalController,
5 public alertCtrl: AlertController,
6 private _contactoService: ContactoService
7 ) {}
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 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.
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 );
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
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:
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 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 }
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();
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
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;
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:
Por:
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
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
Gist⁵⁰
contactos.ts
Del libro:
⁵⁰https://gist.github.com/vihugarcia/5d4681c5fc0cae8b14e30ad86780ffb6
Capítulo 3: Servidor de desarrollo 111
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:
⁵²https://gist.github.com/vihugarcia/e7f0a994c701ad7bd1baca907ac73608
Capítulo 3: Servidor de desarrollo 115
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:
⁵⁴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
Gist⁵⁶
Archivo de configuración de la dirección de la api
Del libro:
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
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:
⁵⁸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:
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>
1 <ion-searchbar [(ngModel)]="terminoBusqueda"></ion-searchbar>
Barra de búsqueda
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 <p>{{fechaNac}}</p>
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>
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
Gist⁶¹
Lo primero que tenemos es el import de los componentes necesarios para trabajar con pipes:
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}}
⁶¹https://gist.github.com/vihugarcia/138e12432f94fb0618b674f78ac0bb3d
Capítulo 4: Búsquedas y filtros 127
1 if (contacts == null) {
2 return [];
3 }
1 if (searchTerm == null) {
2 return contacts;
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 <ion-item-sliding
2 *ngFor="let contacto of (contactos | contactNamePipe:terminoBusqueda)">
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
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;
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
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
• 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
Chrome Inspect
Herramientas de desarrollador
Error de consola
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.
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:
Build de la aplicación
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
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 ruta/a/android/sdk/build-tools/version/zipalign -v 4
2 HelloWorld-release-unsigned.apk HelloWorld.apk
¡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
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
Instalación
Muy bien, después de presionar Next unas cuantas veces, llegaremos al final:
Fin de instalación
Interface gráfica
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