Aplicación CRUD con AngularJS – parte II (Cliente móvil), guardar un objeto en la base de datos.

En el post anterior, veíamos como crear un servicio a nivel de servidor para guardar un estudiante en una base de datos utilizando PHP y MySQL.

En este post, vamos a crear un cliente móvil con IONIC Framework para consumir este servicio.

Creando aplicación móvil:

Inicialmente vamos a crear nuestro proyecto móvil con el template de tabs, como vemos en la imagen:

Denominaremos este proyecto móvil: «colegioDonaRita»:

Podemos previsualizar el proyecto con el comando ionic serve:

El resultado, debería ser el siguiente:

Note que IONIC es «inteligente» según el contexto de la plataforma, es decir, en Android los tabs van ubicados en la parte superior y en iOS, los tabs van ubicados en la parte inferior, esto debido al enfoque «native» que tiene IONIC para que las apps funcionen directamente en nuestros dispositivos.

Alternativas de pre-visualización de la aplicación:

En algunas ocasiones, por diversos problemas con el sistema operativo, etc… no funciona el ionic serve, una alternativa para resolver este problema es ubicar la carpeta www, en un host y así verificar su funcionamiento:

Para visualizar de una forma más adecuada nuestra aplicación móvil, utilizaremos el pluggin Ripple de Google Chrome, que nos permite elegir entre una variedad de dispositivos y emular comportamientos «native» del dispositivo (acelerómetro, GPS, etc.):


Personalizando la aplicación móvil:

Para iniciar la construcción de nuestro cliente móvil con IONIC, empezaremos por personalizar la aplicación creada por defecto. Lo primero que vamos a hacer, es dejar sólo dos tabs (Crear estudiante y Buscar estudiantes).

El primer paso es editar la vista principal templates/tabs.html, esta vista actúa como contenedor de los otros 2 tabs:

Hemos cambiado la definición de los 2 primeros tabs, actualizando las propiedades HTML (title, href) y hemos eliminado el último tab. Dado que actualizamos las rutas en la vista principal templates/tabs.html, ahora debemos actualizar las reglas de navegación en el archivo js/app.js:

En la regla de navegación ‘tab’, podemos observar las propiedades abstract:true, que indica que esta vista actúa solamente como un contenedor de los tabs (guardarestudiante.html y listarestudiante.html), como su nombre lo indica es una vista abstracta y no concreta.

Las vistas de guardar y listar/buscar estudiantes quedan de la siguiente manera:

Con el refactoring que hemos hecho hasta el momento, tiene el siguiente aspecto:

Posteriormente, vamos a definir los nuevos íconos para las funcionalidades de guardar y buscar/listar estudiantes.

Con objeto de cambiar los íconos, editamos el template/vista contenedor de los tabs (templates/tabs.html), como sigue:

Construcción del formulario para capturar datos del estudiante:

Ahora que tenemos la presentación básica de nuestro cliente móvil con IONIC Framework, vamos a construir el formulario para capturar los datos del estudiante que queremos enviar a nuestro servicio (ver post anterior).

Recordemos la definición de nuestra entidad del modelo «Estudiante»:

Para esto, debemos referirnos a la documentación oficial de formularios que expone IONIC Framework. En esta primera versión del formulario para capturar los datos no vamos a validar entradas de usuario (number, que la edad corresponda a un valor positiva, etc…), sin embargo en este link pueden encontrar información sobre como validar entradas de formulario con AngularJS. En este orden de ideas, nuestro formulario luce de la siguiente forma:

Y este es el marcado HTML que lo define:


Programación de la lógica de interacción con el formulario:

Hasta este punto hemos terminado el «diseño gráfico» de nuestra app móvil, por lo tanto, continuaremos con la parte de programación o lógica de nuestra vista, para lo cual AngularJS ha dispuesto del patrón MVC (ver post anterior). Debemos definir entonces un controlador para la vista «templates/tab-guardarestudiante.html«, recordemos que este controlador es el encargado de contener la lógica para la interacción de la vista con el usuario.

Debido a que en nuestra app móvil sólo tenemos 2 vistas (guardar y buscar estudiantes), vamos a definir 2 controllers:

Seguidamente, es necesario entrelazar los controladores con sus respectivas vistas, gráficamente, es algo como esto:

Para lograr enlazar «vista y controller», debemos editar el archivo js/app.js con objeto de especificar en las reglas de navegación qué vista va entrelazada a que controller:

La idea de entrelazar la vista y el controller es poder utilizar las capacidades de AngularJS para automatizar la actualización de la vista, según vayamos modificando el $scope y viscerversa, actualizar el $scope, según el usuario vaya introduciendo datos en el formulario, a esto se le denomina «Two Data Binding«, lo cual no es más que la conexión entre vista y $scope del controller reflejando los cambios en ambos sentidos.

Una representación gráfica de lo que queremos hacer es la siguiente:

Observe que nuestro objetivo es fusionar las propiedades del objeto (estudiante) con la vista (formulario) que visualiza el usuario final, con ello conseguiremos que los datos que ingresa el usuario al formulario, automáticamente queden almacenados en el objeto (estudiante), para esto, debemos utilizar la directiva ng-model en nuestro html:

Vamos a proseguir con el manejo de eventos, en este caso, nuestro único evento es el del botón «Guardar«, AngularJS nos ofrece un mecanismo limpio para la gestión de los eventos con las directivas ng-. Ejemplo: ng-click, ng-focus, etc… Este es un mecanismo limpio, porque separa en su mayor parte el código HTML del código JavaScript.


Enviando los datos del objeto estudiante al servidor:

Ahora que hemos capturado los datos ingresados por el usuario en el objeto estudiante del $scope, requerimos enviar la información de este objeto al servidor que se encargará de procesarlo según la lógica del negocio, en este caso simplemente lo guardará en una base de datos. Para realizar peticiones vía HTTP hacia el servidor, requerimos de algunas utilidades especializadas que realicen esta labor, por lo tanto necesitamos incorporar nuevos objetos y funcionalidades a nuestra aplicación. AngularJS nos ofrece un mecanismo/patrón denominado «Inyección de dependencias» que es la forma como Angular gestiona los objetos y sus dependencias en los diferentes módulos de nuestra app, es decir, en este caso puntual, nuestro controller «guardarEstudianteCtrl», requiere un objeto $http propio de AngularJS para gestionar las solicitudes al servidor.

Para lograr comunicarnos con el servidor, inicialmente, debemos, inyectar el objeto $http, en nuestro controller:

Adicionalmente, debemos especificarle al servidor a través de nuestro request HTTP que deseamos intercambiar datos en formato JSON:

Y por último, en el método guardarEstudiante() del $scope, realizamos la solicitud/request HTTP:

El código completo de nuestro controller (GuardarEstudianteCtrl) queda como sigue:

$http y $sce se utilizan para enviar datos al servidor y formatear strings de recursos (URLs, HTML, etc…) respectivamente.

Cabe anotar, que utilizar el objeto $http dentro del controller no es la forma más ortodoxa de hacerlo, para esto deberíamos utilizar un Factory o un service de AngularJS, esto lo cubriremos más adelante.

Al probar nuestra solución en el browser, obtenemos el siguiente error:

Esto es debido una política de seguridad implementadas por las versiones modernas de los browsers llamada (CORS –Cross Origin Resource Sharing-) y que básicamente consiste en bloquear peticiones/requests desde el browser hacía un dominio diferente, concretamente, nuestro dominio es http://localhost y queremos hacer un request al dominio http://josecarvajal.16mb.com, ante lo cual el navegador, por razones de seguridad bloqueará la petición/respuesta, como podemos ver en el error de la imagen. Ahora, hay varias alternativas de solución a este problema,

  1. Modificar los headers del response en el servidor, autorizando algún dominio para que pueda realizar las peticiones HTTP, el browser leerá estos headers y permitirá la conexión, no obstante, esta solución no es viable la mayoría de los casos donde no tenemos control directo sobre el código servidor.
  2. Utilizar el proxy de IONIC como intermediario entre el browser y el servidor, esta alternativa consiste en configurar nuestra aplicación, y a través del ionic-serve, realizar las peticiones al servidor: navegador proxy IONIC servidor. De esta forma, el browser se comunica con el proxy de IONIC quien a su vez establece comunicación con el servidor.
  3. Utilizar la extensión Ripple de Google Chrome que automáticamente nos permitirá realizar peticiones cross-domain.

Utilizaremos la opción 3 por efectos de sencillez y versatilidad en el post.

Para esto, simplemente corremos nuestra app desde CLI (Command Line Interface):

Una vez desplegado, procedemos a habilitar la extensión Ripple de Google Chrome como sigue:

De esta forma podremos pre-visualizar nuestra app, diligenciamos el formulario, y ahora no tenemos el problema de CORS –Cross Origin Resource Sharing-:

Instalando la aplicación en el dispositivo:

Para cerrar esta segunda parte del Post, vamos a instalar la app en el dispositivo para que funcione de forma nativa.

El primer paso es agregar la plataforma que deseamos, esto, porque IONIC nos permite generar instaladores para Android, iOS, entre otros. Esto lo logramos con el comando «ionic platform add«, recuerde estar ubicar en la carpeta o directorio del proyecto (colegioDonaRita):

Una vez agregada la plataforma de Android, verificamos en el directorio del proyecto que se ha creado la carpeta asociada a este sistema operativo:

Posteriormente, es necesario compilar nuestro proyecto con fines de generar el instalador para la plataforma seleccionada (Android), para esto, utilizaremos el comando «ionic build android«:

Note que al compilar, hemos obtenido el error «[Error: Please install Android target: «android-22«.» Y adicionalmente, se nos sugieren algunas acciones para resolverlo.

Siguiendo la recomendación, vamos a actualizar nuestro SDK y Platform Tools de Android, para ello, corremos el comando «android» en el cmd:

Con esto, estaremos lanzando el «Android SDK Manager» que es la herramienta para gestionar lo relacionado a paquetes de desarrollo del sistema operativo móvil Android, estando aquí, seleccionamos: «Android SDK Tools» y «Android Platform Tools»:

Aceptamos licencias e instalamos:

Una vez actualizado nuestro «Android SDK Tools», debemos cerrarlo y abrirlo de nuevo (escribir «android» en la línea de comandos), con esto ya podemos ver el API 22, de cual instaremos los siguientes paquetes:

Para una mayor velocidad en la instalación, verifique que sólo haya marcado para instalar los paquetes necesarios. Este puede ser un proceso demorado.

Aceptamos licencia e instalamos.

Después de terminar la instalación la instalación del SDK para el API 22, volvemos a ejecutar el comando «ionic build android«:

Ahora podemos observar que el proceso de compilación inicia, es posible que algún momento de la compilación se nos solicite autorización para desbloquear algún programa o puerto del firewall.

Luego de terminar la instalación, es posible que aún sigan saliendo errores por paquetes que faltan instalar:

Es necesario instalar estos paquetes desde el «Android SDK Manager», lanzándolo otra vez con el comando «android«:

Cuando volvemos a ejecutar «ionic build android» en la carpeta del proyecto, obtendremos la compilación exitosa:

Una vez compilado el proyecto, ya disponemos del instalador para Android (archivo con extensión apk), en nuestro caso «android-debug.apk«:

Podemos simplemente enviarnos este instalador a nuestro email e instalarlo desde el dispositivo. IONIC Framework también ofrece otras opciones para ejecutar nuestro app que no cubriremos en este post.

Podemos instalar el «android-debug.apk» en nuestro móvil (este procedimiento puede tener algunas variaciones según las versiones del sistema operativo), es muy posible que realizando la instalación, Android nos solicite permisos para instalar aplicaciones de orígenes desconocidos (diferente a la tienda de Google Play), simplemente aceptamos estos permisos y continuamos:

Una vez instalada la apk en nuestro dispositivo, podremos visualizar el ícono de acceso, y el pantallazo inicial al momento de ejecutarla:

Ahora bien, al momento de probar nuestro app directamente en el dispositivo, nos surge el siguiente error:


Habilitar conexión remota en dispositivo:

Nuestra app no puede comunicarse con el servidor PHP remoto, porque, al igual que hicimos en las pruebas desde el browser WEB, debemos habilitar nuestra app a nivel del dispositivo para que se comunique con el servidor, para esto, debemos instalar el pluggin «cordova-plugin-whitelist» en nuestra aplicación, este plugin nos permitirá enviar peticiones al servidor directamente desde nuestro dispositivo. Estando ubicados en el proyecto «ColegioDonaRita», intentamos instalar el pluggin de la siguiente forma:

ionic plugin add https://github.com/apache/cordova-plugin-whitelist.git

Al realizar esto, es posible que obtendremos el siguiente error:

Para solucionarlo, simplemente descargamos Git que es el software que nos permitirá la descarga del plugin que deseamos incorporar a nuestra app.

Nos dirigimos a https://git-scm.com/downloads:

Una vez descargado, iniciamos el instalador, en el pantallazo «Adjusting your PATH environment«, seleccionar la opción «Use Git and optional Unix tools from the Windows Command Prompt» con objeto de poder utilizar Git desde nuestra línea de comandos Windows:

Posteriormente, intentamos de nuevo la instalación del pluggin cordova-plugin-whitelist con el comando:

ionic plugin add https://github.com/apache/cordova-plugin-whitelist.git

Noten como ahora hemos podido instalar el plugin, gracias a Git.

Una vez hecho esto, recompilamos y reinstalamos nuestra aplicación en el dispositivo.

Al ejecutarla nuevamente, funcionará según lo esperado:

Re factorizando nuestro código para mejorarlo:

Existen varias mejoras y buenas prácticas que podemos introducir en nuestro código, una de ellas es establecer los headers por defecto del objeto $http de Angular en un solo punto centralizado, y de esta forma, evitar hacerlo una y otra vez:

En nuestras aplicaciones AngularJS, tenemos la posibilidad de centralizar ciertos comportamientos en el momento del start-up (inicio) del app, eso lo hacemos en el método run() de nuestro módulo en el archivo app.js. Note que debemos inyectar el objeto $http en la función del método run():

Otra mejora es incorporar el código de nuestro negocio en un Factory o un Service de AngularJS de tal forma que podamos reutilizar nuestra lógica de guardar un estudiante desde diferentes puntos de nuestra app. Para esto, simplemente creamos un Factory en el módulo de services y lo inyectamos en nuestro «GuardarEstudianteCtrl«.

El Factory para reutilizar el código quedaría con el siguiente aspecto:

Desde el controller inyectamos el estudianteFactory y llamamos su método guardarEstudiante(), enviando el objeto estudiante del $scope como parámetro:

Una última mejora que vamos a realizar es centralizar la URL del servidor como una constante, esto nos permitirá cambiar nuestro proveedor de servicios fácilmente en un solo punto, sin necesidad de modificar todos los controllers o factories donde consumimos servicios vía HTTP. AngularJS nos da la posibilidad definir constantes en nuestras aplicaciones, para hacerlo, utilizamos el método constant() en nuestra app:

Puede notar que he definido la URL solo hasta /services/, es decir, falta «/persistirEstudianteService.php«, esto ha sido a propósito, dado que con la misma URL sólo tendremos que completarla con el nombre del servicio y minimizar los cambios. Una vez hayamos creado la constante, podemos utilizarla/inyectarla donde la necesitemos:

Con esto, ya hemos terminado un cliente móvil para guardar un estudiante en un servidor. En el próximo post, realizaremos la lectura de los estudiantes guardados en el servidor y los mostraremos en nuestro dispositivo.

35 comentarios en “Aplicación CRUD con AngularJS – parte II (Cliente móvil), guardar un objeto en la base de datos.

  1. Cordial saludo Sr Jose de pronto usted me pueda sacar de una duda que no he podido resolver yo tengo en mi servidor php mysql una url con un json para listar unos datos cuando un usuario se loguee de esta forma http://enturneapp.enturne.co/sfLibres/sesion?usr=conductor.prueba&pass=123 si yo lo pongo asi directamente en el controlador me funciona perfecto o http://appenturne.enturne.co/sfLibres/sesion/usr/conductor.prueba/pass/123

    .controller(«JsonCtrl»,function($scope,$http){

    $scope.posts=[];

    $http.get(«http://enturneapp.enturne.co/sfLibres/sesion?usr=conductor.prueba&pass=123»).success(function(data){
    console.log(data);
    $scope.posts=data;
    })
    .error(function(err) {
    console.log(data);
    $scope.posts=data;
    });

    })

    pero lo que yo quiero es recoger del formulario login esos dos valores user y pasw y poder mandarlos al $http.get ya que con cada usuario obviamente el json genera datos particulares, he probado esto pero siempre me da usuario y contraseña incorrectos que es lo que genera el json en su array cuando no recibe los datos de usuario y passw correctos, este es mi form y la que pongo en el controller q no me funciona:

    http://lib/ionic/js/ionic.bundle.js

    http://js/app.js
    http://js/controllers.js

    Conductor
    Info

    El controlador:

    .controller(«JsonCtrl»,function($scope,$http){

    $scope.posts=[];
    $scope.user=»;
    $scope.pasw=»;

    $http.get(«http://appenturne.enturne.co/sfLibres/sesion?usr=’+$scope.user+’&pass=$scope.pasw»).success(function(data){
    console.log(data);
    $scope.posts=data;
    })
    .error(function(err) {
    console.log(data);
    $scope.posts=data;
    });

    })

    .controller(«EstadoCtrl»,function($scope){

    })

    Seguro tu daras con la respuesta creo que es algo sencillo pero yo apenas comienzo con esta aventura y estoy un poco enredado.

    Me gusta

  2. Hola Quisiera saber si al hacer guardar la info de un estudiante lo cual hiciste en la segunda parte, desde el cliente movil que viene a se un cliente web. No te da esa reestriccion de No-Cross Domain, dicha reestriccion tiene Javascript. Tuviste algun problema con eso. O de plano Hostinger te permite hacer por default ese tipo de servicios Cross-Domain?. Gracias!!

    Me gusta

  3. buenas noches, tengo un problema, y es que no me devuelve nada al pulsar el boton «guardar»

    mi controllers es el siguiente

    angular.module(‘starter.controllers’, [])

    .controller(‘saveEstudianteCtrl’, function($scope, $http, $sce) {

    var defaultHTTPHeaders = {
    ‘Content-Type’: ‘application/json’,
    ‘Accept’: ‘application/json’
    };

    $http.defaults.headers.post = defaultHTTPHeaders;

    $scope.estudiante =
    {
    documento : »,
    nombre : »,
    genero : »
    };

    $scope.guardarEstudiante = function () {
    var urlCompleta = ‘http://localhost/ionic/persistirEstudianteService.php’;
    var postUrl = $sce.trustAsResourceUrl(urlCompleta);
    $http.post(postUrl, estudianteObj)
    .then(
    function () {
    alert(‘el estudiante se ha guardado con exito’);
    },
    function () {
    alert(‘Error al guardar el estdudiante’);
    }
    );
    };
    })

    .controller(‘readEstudianteCtrl’, function($scope, $http) {

    });

    Me gusta

    • Mi hermano no te hace nada porque en el post tienes como segundo argumento el estudianteObj, cambialo por $scope.estudiante y listo……. una recomendacion para estos errores que no son mostrados usa la herramienta de desarrollador que trae el navegador, en la parte de consola podras ver mas a fondo esos errores!

      Me gusta

  4. Hola! Que buen tutorial, no había encontrado uno que explicara paso a paso la creación de una app que funcione bien, la he seguido paso a paso pero me dio el siguiente error al ejecutarla:

    ‘0 379536 error Error: [$http:badreq] Http request configuration url must be a string. Received: {}
    http://errors.angularjs.org/1.5.3/$http/badreq?p0=%7B%7D
    minErr/<@http://localhost:8100/lib/ionic/js/ionic.bundle.js:13438:12
    $http@http://localhost:8100/lib/ionic/js/ionic.bundle.js:24470:15
    createShortMethodsWithData/ Function:2:239
    @http://localhost:8100/lib/ionic/js/ionic.bundle.js:65427:9
    $RootScopeProvider/this.$get</Scope.prototype.$eval@http://localhost:8100/lib/ionic/js/ionic.bundle.js:30395:16
    $RootScopeProvider/this.$get</Scope.prototype.$apply@http://localhost:8100/lib/ionic/js/ionic.bundle.js:30495:20
    @http://localhost:8100/lib/ionic/js/ionic.bundle.js:65426:7
    defaultHandlerWrapper@http://localhost:8100/lib/ionic/js/ionic.bundle.js:16787:3'

    Según entiendo el parámetro estudianteObj del método $http.post(postUrl,estudianteObj) es el del problema, ya busqué alguna forma de solucionarlo pero no encontré nada que me sirviera, será que me puedes ayudar? La base de datos y el servicio PHP los tengo locales desde XAMPP, de ahí todo lo he hecho igual al tutorial.

    Gracias de antemano, saludos.

    Me gusta

    • un poco tarde pero a alguien le puede ayudar.

      //LA FUNCION DEBE QUEDAR ALGO ASI
      $scope.guardaEstudiante = function () {
      var urlCompleta = ‘http://ejemplo.com/persistirEstudianteService.php’;
      //CON ESTOS 2 PARAMETROS FUNCIONA BIEN «urlCompleta» Y «$scope.estudiante»
      $http.post(urlCompleta, $scope.estudiante)
      .then(
      function () { //Funcion a ejecutarse en caso de exito
      alert(‘El usuario se registro con exito’);
      },
      function () { //Funcion a ejecutarse en caso de error
      alert(‘Error al registrar al usuario’);
      }
      );

      };

      //AHORA EN EL ARCHIVO «persistirEstudianteService.php» QUE ESTA EN EL HOST
      //DEBES AGREGAR AL PRINCIPIO ESTAS LINEAS PARA ACTIVAR EL CORS
      if (isset($_SERVER[‘HTTP_ORIGIN’])) {
      header(«Access-Control-Allow-Origin: {$_SERVER[‘HTTP_ORIGIN’]}»);
      header(‘Access-Control-Allow-Credentials: true’);
      header(‘Access-Control-Max-Age: 86400’);
      }

      if ($_SERVER[‘REQUEST_METHOD’] == ‘OPTIONS’) {

      if (isset($_SERVER[‘HTTP_ACCESS_CONTROL_REQUEST_METHOD’]))
      header(«Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS»);

      if (isset($_SERVER[‘HTTP_ACCESS_CONTROL_REQUEST_HEADERS’]))
      header(«Access-Control-Allow-Headers: {$_SERVER[‘HTTP_ACCESS_CONTROL_REQUEST_HEADERS’]}»);
      }

      Y LISTO.

      Me gusta

  5. Hola muy buen aporte, una pregunta, de que manera podria guardar los datos en el telefono si no tiene conexion a internet, es recomendable crear un archivo json y que se guarde ahi y cuando tenga conexion se envie? o hay algun tipo de base de datos NoSQL para android?, ya que mi bd es en mongoDB.

    Espero alguin me pueda asesorar, soy un poco nuevo en ionic.

    Saludos.

    Me gusta

  6. Hola Roberto,

    Gracias por el comentario, las búsquedas no varían mucho, tengo pendiente actualizar el tutorial para mostrar esa parte. En general, para las búsquedas simplemente se recuperan los datos del server vía AJAX y se muestran con un ng-repeat en el móvil.

    Me gusta

Deja un comentario