Navegación en Ionic

Alexander A. Ramírez M.

Ya hemos avanzado en el uso de muchas de las funcionalidades de Ionic por el lado del JavaScript. Hasta ahora hemos trabajado en una sola pantalla. Las aplicaciones en general tienen varias pantallas y se debe ofrecer una forma de navegar en la aplicación y además de avanzar y poder regresar a pantallas previas.

ionic ofrece para ello la directiva ion-nav-view. Esta permite mantener una o varias historias de navegación. El componente sobre el cual se apoya el framework es ui-router el cual se basa en el concepto de estados en lugar de el concepto de rutas, un estado es un lugar en la aplicación, estos lugares están descritos a través del controlador, plantillas y las vistas. Es decir, es posible mantener una jerarquía de estados, es decir, un estado hereda las propiedades de otro estado padre o dicho de otra manera, es posible tener un estado padre que ofrece elementos que todos sus hijos pueden aprovechar. Además se pueden tener vistas paralelas dentro de un estado (parallel nested views).

Ingrese a play.ionic.io para obtener el código para iniciar el tutorial y haga FORK. No olvide hacer FORK.

El objetivo es:

  • Crear una aplicación con múltiples páginas (o vistas).
  • Crear las vistas, una de presentación, la segunda una lista de empleados, la tercera el detalle del empleado y la cuarta con información de contacto.
  • Definir los estados de la aplicación con ui-router.
  • Implementar la navegación mediante el uso de ui-sref.

Estamos partiendo de una aplicación sin nada. Entonces debemos definir el controlador de la aplicación en HTML.

<body ng-app="app" ng-controller="viewController">

Ahora hay que implementar el controlador (vacio por ahora) en JS.

.controller('viewController', function($scope) {
});

Ahora vamos a utilizar las directivas que ofrecen la posibilidad de mantener memoria sobre la navegación del usuario (es decir, los estados por los cuales has navegado). Esta historia no es única, hay una por estado.

Las directivas que hay que estudiar son:

  • ion-nav-view - Se utiliza para dibujar (render) las plantillas. Estas plantillas están asociadas a algún estado y a un controlador. Esta asociación se realiza en la configuración de ui-router. Esta directiva es la que finalmente se expande a la plantilla que corresponda según el estado en el que se encuentra la aplicación. Es decir, es como un “frame” donde se dibuja la plantilla correspondiente.
  • ion-view - Es un contenedor del contenido de la vista. Es hijo de ion-nav-view. Es este se definen el encabezado (header) o los elementos de navegación.
  • ion-nav-bar - Es la barra superior, donde se puede colocar un título (ya sea texto o algo más complejo en HTML) y los botones de navegación izquierdo y derecho. Puede ser sobreescrita por los estados subsiguientes o modificada en su comportamiento.
  • ion-nav-back-button - Crea un botón de navegación hacia el estado previo. Este botón aparece si hay una historia de navegación previa. El comportamiento y la apariencia del botón se pueden modificar.
  • ion-nav-buttons - Se utiliza para definir los botones que debe aparecer en el ion-nav-bar. Esta definición se realiza dentro del ion-view. Estos botones sobreescriben o reemplazan los que se hayan definido previamente. Es un descendiente inmediato de ion-view.
  • ion-nav-title - Se utiliza para definir un título específico en HTML para la vista dentro del ion-view.
  • nav-transition - Permite indicar el tipo de transición que se debe utilizar para pasar de una vista a otra.
  • nav-direction - Permite indicar la dirección en la cual se debe animar la transición de una vista a otra.

Dentro del cuerpo del HTML definamos la barra de navegación mediante la directiva ion-nav-bar, el contenedor de las plantillas ion-nav-view. Además definamos una primera plantilla que será la presentación de la empresa pres.html.

<ion-nav-bar  class="bar-assertive">
<ion-nav-title>
  Padre
</ion-nav-title>
</ion-nav-bar>

<ion-nav-view>
</ion-nav-view>

<script id="states/pres/pres.html" type="text/ng-template">
  <ion-view view-title="Presentación">
    <ion-nav-title>
      Hijo!
    </ion-nav-title>
    <ion-content class="padding">
      <h1>Presentación contenido</h1>
    </ion-content>
  </ion-view>
</script>

Hay varias pruebas que debe hacer para entender la prioridad de los títulos, como eliminar el view-title y ver el resutlado y también eliminar el ion-nav-title de la plantilla y ver el resultado.

Para ver el resulado ahora definamos el estado. La definición de un estado básicamente consiste en asociar una plantilla con un controlador bajo un nombre. Además se puede asociar una ruta. Esto se hace mediante la rutina config. Se deben inyectar los servicios $stateProvider y $urlRouterProvider. La rutina state toma como parámetros el nombre del estado y el objeto que especifica sus atributos. En particular url, templateUrl y controller. url sirve como ruta para ser utilizada con href. El nombre del estado se puede utilizar con ui-sref. templateUrl es la plantilla que se utiliza y controller el controlador que asocia a la plantilla con el objeto $scope.

.config(function($stateProvider, $urlRouterProvider){
  $stateProvider
    .state('pres', {
      url: '/pres',
      templateUrl: 'states/pres/pres.html',
      controller: 'presController'
    });
  $urlRouterProvider.otherwise('/pres');
});

Ahora en la plantilla que ya definimos vamos a agregar un botón para navegar a la lista de empleados.

<ion-content class="padding">
  <h1>Presentación contenido</h1>
  <button ui-sref="empleados" class="button button-block">Ver el equipo de trabajo</button>
</ion-content>

Ahora tenemos un botón que nos debería llevar al estado empleados que no está definido. En este caso estamos usando la directiva ui-sref que permite navegar de un estado a otro utilizando el nombre del estado y no la ruta. No se está utilizando el atributo url definido en la configuración del estado (stateConfig).

Agreguemos la plantilla de la lista de empleados y definamos el estado empleados.

<script id="states/empleados/empleados.html" type="text/ng-template">
  <ion-view view-title="Empleados">
    <ion-content class="padding">
      <div class="list">
        <div class="item item-divider item-assertive item-icon-left">
          <i class="icon ion-android-people"></i> 
          Nuestro talento
        </div>
        <a ng-href="#/empleado/ { { e.id } }" class="item item-avatar" 
          ng-repeat="e in empleados">
          <img ng-src=" { { e.foto } }">
          
        </a>
      </div>
    </ion-content>
  </ion-view>
</script>

En el controlador debemos definir el estado y también la lógica del controlador.

.state('empleados', {
  url: '/empleados',
  templateUrl: 'states/empleados/empleados.html',
  controller: 'emplController'
})

Y el controlador queda de la siguiente manera.

.controller('emplController', function($scope, datoFactory) {
  $scope.empleados = datoFactory.empleados();
})

Haga un Factory con los datos y los métodos empleados y empleado que toma el identificador del empleado.

.factory('datoFactory', function() {
return {
  datos: [
  { 
    id: 1, 
    nombre: "Geraldine Ganaim", 
    cargo: "Ingeniero de Proyectos", 
    foto: "http://placehold.it/48x48"
  },
  { 
    id: 2, 
    nombre: "Jonathan Duarte", 
    cargo: "Ingeniero de Proyectos", 
    foto: "http://placehold.it/48x48"
  },
  { 
    id: 3, 
    nombre: "David Prieto", 
    cargo: "Ingeniero de Proyectos", 
    foto: "http://placehold.it/48x48"
  },
  { 
    id: 4, 
    nombre: "María Rodríguez", 
    cargo: "Ingeniero de Proyectos", 
    foto: "http://placehold.it/48x48"
  }
  ],
  empleados: function() {
    return this.datos;
  },
  empleado: function(id) {
    var i;
    for(i=0; i<this.datos.length;i++) {
      if (this.datos[i].id == id) {
        return this.datos[i];
      }
    }
    return {};
  }
};

Ahora nos corresponde hacer la pantalla para mostrar el detalle de cada empleado y la otra pantalla para la información de contacto. De forma análoga debemos, crear las plantillas, configurar los dos nuevos estados y crear los controladores. En cada controlador hay que colocar en el modelo ($scope) los datos que consume la vista. Agreguemos adicionalmente un botón en la pantalla de presentación para acceder a los datos de contacto.

<ion-nav-bar  class="bar-assertive">
<ion-nav-title>
  Padre
</ion-nav-title>
</ion-nav-bar>

<ion-nav-view>
</ion-nav-view>

<script id="states/pres/pres.html" type="text/ng-template">
  <ion-view view-title="Presentación">
    <ion-nav-title>
      Hijo!
    </ion-nav-title>
<ion-content class="padding">
  <h1>Presentación contenido</h1>
  <button ui-sref="empleados" class="button button-block">Ver el equipo de trabajo</button>
  <button ui-sref="contacto" class="button button-block">Ver datos de contacto</button>
</ion-content>
  </ion-view>
</script>

<script id="states/empleados/empleados.html" type="text/ng-template">
  <ion-view view-title="Empleados">
    <ion-content class="padding">
      <div class="list">
        <div class="item item-divider item-assertive item-icon-left">
          <i class="icon ion-android-people"></i> 
          Nuestro talento
        </div>
        <a ng-href="#/empleado/ { { e.id } }" class="item item-avatar" 
          ng-repeat="e in empleados">
          <img ng-src=" { { e.foto } }">
          { { e.nombre } }
        </a>
      </div>
    </ion-content>
  </ion-view>
</script>

<script id="states/detalle/detalle.html" type="text/ng-template">
  <ion-view view-title="Empleado">
    <ion-content class="padding">
      <div class="card">
        <div class="item item-assertive">
          { { empleado.id } }
        </div>
        <div class="item item-thumbnail">
          <img ng-src=" { { empleado.foto } }">
          { { empleado.nombre } }
        </div>
      </div>
    </ion-content>
  </ion-view>
</script>

<script id="states/contacto/contacto.html" type="text/ng-template">
  <ion-view>
    <ion-content class="padding">
      Datos de contacto
    </ion-content>
  </ion-view>
</script>

El JS debe quedar de la forma siguiente:

angular.module('app', ['ionic'])

.controller('presController', function($scope) {

})

.controller('emplController', function($scope, datoFactory) {
  $scope.empleados = datoFactory.empleados();
})

.controller('detaController', function($scope, datoFactory, $stateParams) {
  $scope.empleado = datoFactory.empleado($stateParams.id);
})

.controller('contController', function($scope) {
  $scope.contactInfo = {
    
  };
})

.config(function($stateProvider, $urlRouterProvider){
  $stateProvider
    .state('pres', {
      url: '/pres',
      templateUrl: 'states/pres/pres.html',
      controller: 'presController'
    })
    .state('empleados', {
      url: '/empleados',
      templateUrl: 'states/empleados/empleados.html',
      controller: 'emplController'
    })
    .state('detalle', {
      url: "/empleado/{id:int}",
      templateUrl: 'states/detalle/detalle.html',
      controller: 'detaController'
    })
    .state('contacto', {
      url: '/contacto',
      templateUrl: 'states/contacto/contacto.html',
      controller: 'contController'
    });
    
  $urlRouterProvider.otherwise('/pres');
})

.factory('datoFactory', function() {
  return {
    datos: [
    { 
      id: 1, 
      nombre: "Geraldine Ganaim", 
      cargo: "Ingeniero de Proyectos", 
      foto: "http://placehold.it/48x48"
    },
    { 
      id: 2, 
      nombre: "Jonathan Duarte", 
      cargo: "Ingeniero de Proyectos", 
      foto: "http://placehold.it/48x48"
    },
    { 
      id: 3, 
      nombre: "David Prieto", 
      cargo: "Ingeniero de Proyectos", 
      foto: "http://placehold.it/48x48"
    },
    { 
      id: 4, 
      nombre: "María Rodríguez", 
      cargo: "Ingeniero de Proyectos", 
      foto: "http://placehold.it/48x48"
    }
    ],
    empleados: function() {
      return this.datos;
    },
    empleado: function(id) {
      var i;
      for(i=0; i<this.datos.length;i++) {
        if (this.datos[i].id == id) {
          return this.datos[i];
        }
      }
      return {};
    }
  };
});

Si se dan cuenta en la aplicación no se puede navegar a la pantalla previa. Esto se hace de una manera muy sencilla en ionic y además ofrecen algunas opciones de configuración.

<ion-nav-bar class="bar-positive">
 <ion-nav-back-button></ion-nav-back-button>
</ion-nav-bar>

Ahora después de navegar se puede ir al estado previo utilizando el botón superior a la izquierda.

Hemos configurado el título de forma estática. La verdad es que se puede colocar un título dinámico. Modifique una de las plantillas y agregue una expresión para colocar en el título el valor de la variable titulo.

<ion-view view-title="">

Defina la variabla titulo en el controlador correspondiente.

  $scope.titulo = 'EL TITULO QUE DESEE COLOCAR';

Esta facilidad es útil cuando se desea colocar un título que proviene de los datos que se descargan.

Como nota aparte hay un servicio que provee ionic denominado $ionicConfigProvider. Este servicio ofrece opciones de configuración en la aplicación que permite cambiar el comportamiento del botón de regreso (Back button). Se puede cambiar el ícono, el texto por defecto o si el título de la pantalla previa debería ser el texto del botón para regresar.

Si desea, puede ver el resultado en play.ionic.io. El que hicimos en clase.

Veamos el resultado.

Adicionalmente pueden ver el ejemplo de la página de ionic.