Introducción a Ionic: Navegación
() translation by (you can also view the original English article)



Ya hemos cubierto bastante en esta serie, incluyendo la navegación. Sin embargo, Ionic proporciona algunos componentes que proporcionan características adicionales para construir una navegación más funcional. En este tutorial, agregamos los componentes del menú lateral y de las pestañas a la aplicación, y también observamos algunos servicios adicionales para que la navegación de nuestra aplicación sea más inteligente.
Archivos del Tutorial
Los archivos del proyecto de este tutorial están disponibles en GitHub. La premisa general de la aplicación es que muestra cierta información sobre las instalaciones locales. En este tutorial, agregamos la capacidad de mostrar bibliotecas, museos, parques y hospitales. Actualmente, solo se muestran ubicaciones en Chicago, esto es algo que solucionaremos en el próximo tutorial.
Puedes descargar el proyecto completo de este tutorial desde GitHub. Si clonas el proyecto, también puedes codificarlo utilizando Git y ejecutando git checkout -b start
. El último ejemplo también está disponible para previsualizar.
Ten en cuenta que he eliminado la resolución de la vista del lugar que teníamos en la tercera parte de esta serie. No quiero cubrirlo en profundidad, pero el controlador carga los datos ahora y simplifica nuestra navegación.
1. Agrega un menú lateral
Uno de los patrones de navegación más comunes en las aplicaciones móviles es un menú lateral. Este es un cajón que se desliza hacia un lado y expone enlaces de navegación y tal vez otro contenido, como el estado actual del inicio de sesión. Por diseño, están fuera de la pantalla y se abren con algún tipo de botón, a menudo el icono de la hamburguesa, aunque las personas no están de acuerdo en el uso de ese icono.



A menudo, los menús laterales pueden abrirse deslizándolo por el lado para abrirlo u ocultarlo empujándolo hacia abajo. Esto puede ser útil, pero a veces puede ponerse en el camino de otros gestos y debes estar pendiente de que no entre en conflicto. Deberías considerar mejor el uso del barrido con toda la visión y experiencia de tu aplicación en mente, y si hay una preocupación, puedes desactivarla.
Ionic proporciona un par de componentes que hacen que la configuración de un menú lateral sea trivial. Puedes crear hasta dos menús laterales, uno a la derecha y otro a la izquierda. Un menú lateral consta de varios componentes, ionSideMenus
, ionSideMenu
y ionSideMenuContent
.
Para ver esto en acción, vamos a actualizar www/index.html y configurar un menú lateral. Reemplaza el contenido existente con el siguiente código, el cual agrega los componentes del menú lateral alrededor de nuestro código existente.
1 |
<body ng-app="App"> |
2 |
<ion-side-menus>
|
3 |
<ion-side-menu side="left"> |
4 |
<ion-header-bar>
|
5 |
<h1 class="title">Civinfo</h1> |
6 |
</ion-header-bar>
|
7 |
<ion-content>
|
8 |
<ion-list>
|
9 |
<ion-item ui-sref="places" menu-close>Places</ion-item> |
10 |
<ion-item ui-sref="settings.preferences" menu-close>Settings</ion-item> |
11 |
</ion-list>
|
12 |
</ion-content>
|
13 |
</ion-side-menu>
|
14 |
<ion-side-menu-content drag-content="false"> |
15 |
<ion-nav-bar class="bar-balanced"> |
16 |
<ion-nav-buttons side="left"> |
17 |
<button menu-toggle="left" class="button button-icon icon ion-navicon"></button> |
18 |
</ion-nav-buttons>
|
19 |
<ion-nav-back-button class="button-clear"> |
20 |
<i class="ion-arrow-left-c"></i> Back |
21 |
</ion-nav-back-button>
|
22 |
</ion-nav-bar>
|
23 |
<ion-nav-view></ion-nav-view>
|
24 |
</ion-side-menu-content>
|
25 |
</ion-side-menus>
|
26 |
</body>
|
Para habilitar un menú lateral, empezamos envolviendo el contenido de nuestra aplicación en ionSideMenus
. Permite a Ionic coordinar el menú lateral y las áreas de contenido. Luego tenemos un ionSideMenu
con un atributo side="left"
para designar el lado que ocupa.
En el menú lateral, podemos poner cualquier contenido que deseemos. En este caso, y probablemente el escenario más común, el contenido es un componente ionHeaderBar
y un componente ionList
para representar el título de la aplicación y una lista de enlaces, respectivamente. Todavía no hemos definido la vista de configuración, por lo que el enlace fallará por el momento. También ten en cuenta que los componentes ionItem
tienen un atributo menu-close
. Esto cierra automáticamente el menú lateral cuando un usuario hace clic en el enlace, de lo contrario permanecerá abierto.
El componente ionSideMenuContent
se utiliza para contener el área de contenido principal. Esta área de contenido ocupa toda la pantalla, pero este componente sólo ayuda a que el componente del menú secundario se procese correctamente. También hemos utilizado el atributo drag-content="false"
para desactivar los gestos deslizantes ya que interfieren con la lista de desplazamiento y las pestañas.
También hemos añadido un nuevo botón a la barra de navegación con ionNavButtons
. Este es el icono del menú lateral que aparece en la parte superior derecha como tres líneas apiladas. Este botón tiene el atributo menu-toggle="left"
, que activa el menú del lado izquierdo para alternar cuando se selecciona.
Ahora que nuestro menú lateral está en su lugar, vamos a trabajar en la configuración del siguiente componente de navegación principal mediante la adición de pestañas para la vista de configuración.
2. Pestañas con el historial de navegación individual
Las pestañas son otro patrón de navegación común para navegar por una aplicación. Las pestañas son fáciles de entender porque las vemos en tantos tipos de interfaces, no sólo en aplicaciones móviles.
Las pestañas pueden ser apátridas o pueden ser un estado. Una pestaña que muestra contenido y que no retiene memoria de ningún cambio es apátrida, mientras que una pestaña que mantiene 'un estado' basado en la interacción del usuario tiene un estado (por ejemplo, la persistencia de un resultado de búsqueda). Miremos cómo crear pestañas de estado con iconos ya que son más complejas y más potentes.



Configurar las pestañas es bastante fácil con los componentes ionTabs
y ionTab
. Al igual que los menús laterales, se ponen tantos componentes de pestaña dentro como desees. No hay un límite duro, pero creo que cinco es un máximo saludable. En dispositivos más pequeños, demasiados iconos hacen que sea difícil seleccionar una pestaña.
Vamos a configurar las fichas creando un par de nuevos archivos. En primer lugar, vamos a configurar la plantilla mediante la creación de un nuevo archivo en www/views/settings/settings.html. Añade el siguiente código al nuevo archivo.
1 |
<ion-tabs class="tabs-icon-top tabs-stable"> |
2 |
|
3 |
<ion-tab title="Preferences" icon-on="ion-ios-gear" icon-off="ion-ios-gear-outline" ui-sref="settings.preferences"> |
4 |
<ion-nav-view name="preferences"></ion-nav-view> |
5 |
</ion-tab>
|
6 |
|
7 |
<ion-tab title="About" icon-on="ion-ios-information" icon-off="ion-ios-information-outline" ui-sref="settings.about"> |
8 |
<ion-nav-view name="about"></ion-nav-view> |
9 |
</ion-tab>
|
10 |
|
11 |
</ion-tabs>
|
El componente ionTabs
se utiliza para envolver los componentes internos del ionTab
. Hay varias clases que pueden definir cómo aparecen las pestañas, como poner pestañas en la parte superior o inferior, usar iconos con o sin títulos y más. Aquí, hemos decidido utilizar las pestañas que tienen un título con el icono en la parte superior con el color predeterminado.
El componente ionTab
tiene una serie de atributos que se pueden utilizar para definir su comportamiento. Esto soporta muchas características, como mostrar una pequeña insignia de notificación, vincular pestañas a estados, comportamiento de iconos y más. Para nuestras pestañas, cada una tiene un title
, una clase de icono para cuando la pestaña está activa (icono-on
) o inactiva (icono-off
), y enlaces a un estado usando ui-sref
.
Dentro de cada pestaña hay otro ionNavView
. Esto puede parecer fuera de lugar ya que ya tenemos un ionNavView
creado en index.html. Lo que estamos haciendo es declarar ubicaciones adicionales que un estado puede representar, y pueden considerarse como vistas secundarias.
Cada pestaña puede tener su propio historial de navegación, ya que cada ionNavView
es independiente de los demás. Cada pestaña también tiene un nombre único, y será útil para que podamos definir ciertos estados para que aparezcan en el nombre ionNavView
.
Es posible que hayas notado que no hay ningún elemento ionView
en esta página y que es importante tener en cuenta al utilizar pestañas con estado. No es necesario cuando se utiliza ionTabs
de esta manera, sólo si se utilizan las fichas sin estado, la versión del componente CSS, lo necesitaría.
Ahora necesitamos establecer algunos estados adicionales para que el ejemplo funcione. Crea otro archivo en www/views/settings/settings.js y añade el siguiente código.
1 |
angular.module('App') |
2 |
.config(function($stateProvider, $urlRouterProvider) { |
3 |
$stateProvider.state('settings', { |
4 |
url: '/settings', |
5 |
abstract: true, |
6 |
templateUrl: 'views/settings/settings.html' |
7 |
})
|
8 |
.state('settings.about', { |
9 |
url: '/about', |
10 |
views: { |
11 |
about: { |
12 |
templateUrl: 'views/settings/tab.about.html' |
13 |
}
|
14 |
}
|
15 |
})
|
16 |
.state('settings.license', { |
17 |
url: '/license', |
18 |
views: { |
19 |
about: { |
20 |
templateUrl: 'views/settings/tab.license.html' |
21 |
}
|
22 |
}
|
23 |
})
|
24 |
.state('settings.preferences', { |
25 |
url: '/preferences', |
26 |
views: { |
27 |
preferences: { |
28 |
controller: 'PreferencesController', |
29 |
controllerAs: 'vm', |
30 |
templateUrl: 'views/settings/tab.preferences.html' |
31 |
}
|
32 |
}
|
33 |
});
|
34 |
|
35 |
$urlRouterProvider.when('/settings', '/settings/preferences'); |
36 |
})
|
37 |
.controller('PreferencesController', function(Types) { |
38 |
var vm = this; |
39 |
|
40 |
vm.types = Types; |
41 |
});
|
Puedes ver que estamos estableciendo varios estados nuevos, pero estos parecen diferentes de otros estados que hemos definido hasta ahora. El primer estado es un estado abstracto, el cual es esencialmente un estado que no puede ser cargado directamente por su cuenta y tiene hijos. Esto tiene sentido para nosotros con la interfaz de las fichas porque el estado de settings
carga la plantilla de componentes de las pestañas, pero los usuarios nunca están en el componente de pestañas. Siempre están viendo la pestaña activa, que contiene otro estado. Así que usar el resumen nos da esta habilidad de cablear estos correctamente.
Los otros tres estados se definen como settings.[name]
. Esto nos permite definir una relación padre-hijo entre estos estados, y refleja esencialmente la relación padre-hijo de los componentes ionTabs
y ionTab
. Estos estados utilizan la propiedad view, la cual es un objeto con una propiedad con nombre para la vista que se va a usar.
El nombre que le das a tu plantilla con ionNavView
debe coincidir con el nombre de la propiedad. El valor de esa propiedad es entonces la misma definición de estado, sin la URL
que se declaró de la manera habitual. La url
también sigue la relación padre-hijo combinando los dos. Así que todos estos estados hijos son como /settings/preferences.
Necesitas agregar settings.js a index.html usando otra etiqueta de script. Una vez que lo hayas hecho, verás algunos errores porque estamos haciendo referencia a una serie de archivos que aún no hemos creado. Terminemos con nuestras plantillas de las pestañas.
1 |
<script src="views/settings/settings.js"></script> |
Necesitamos crear tres. Los dos primeros son de contenido estático, así que no voy a repasarlos en detalle. Crea un archivo en www/views/settings/tab.about.html y añádele el siguiente contenido.
1 |
<ion-view view-title="About" hide-back-button="true"> |
2 |
<ion-content>
|
3 |
<div class="list"> |
4 |
<a href="https://github.com/gnomeontherun/civinfo-part-3" target="_blank" class="item"> |
5 |
<h2>Project on GitHub</h2> |
6 |
<p>Click to view project</p> |
7 |
</a>
|
8 |
<div class="item" ui-sref="settings.license"> |
9 |
<h2>License</h2> |
10 |
<p>See full license</p> |
11 |
</div>
|
12 |
</div>
|
13 |
</ion-content>
|
14 |
</ion-view>
|
Esto contiene una plantilla que muestra cierta información. Se enlaza con el proyecto GitHub y la licencia. Esto debe lucir así:



Crea otro archivo en www/views/settings/tab.license.html y añade el siguiente contenido.
1 |
<ion-view view-title="License"> |
2 |
<ion-content>
|
3 |
<div class="card"> |
4 |
<div class="item item-divider"> |
5 |
The MIT License (MIT) |
6 |
</div>
|
7 |
<div class="item item-text-wrap"> |
8 |
<p>Copyright (c) 2016 Jeremy Wilken</p> |
9 |
<p>Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:</p> |
10 |
<p>The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.</p> |
11 |
<p>THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.</p> |
12 |
</div>
|
13 |
</div>
|
14 |
</ion-content>
|
15 |
</ion-view>
|
Contiene el contenido de la licencia (MIT) para este código. Hay una tarjeta simple para incluir el contenido. Así debería verse.



La plantilla final contiene algunos elementos de formulario. Lo repasaré con un poco más de detalle. Crea un archivo nuevo en www/views/settings/tab.preferences.html y añádele el siguiente contenido.
1 |
<ion-view view-title="Preferences" hide-back-button="true"> |
2 |
<ion-content>
|
3 |
<ul class="list"> |
4 |
<li class="item item-divider"> |
5 |
Types of Locations |
6 |
</li>
|
7 |
<li class="item item-toggle" ng-repeat="type in vm.types"> |
8 |
{{type.type}} |
9 |
<label class="toggle"> |
10 |
<input type="checkbox" ng-model="type.enabled"> |
11 |
<div class="track"> |
12 |
<div class="handle"></div> |
13 |
</div>
|
14 |
</label>
|
15 |
</li>
|
16 |
</ul>
|
17 |
</ion-content>
|
18 |
</ion-view>
|
Esta vista contiene una lista de palancas que muestran los cuatro tipos de lugares que puede mostrar la aplicación: museo, parque, biblioteca y hospital. Cada uno de estos elementos de la lista te permite habilitar o deshabilitar un tipo de lugar que se encuentre en la lista. El botón de palanca es un componente CSS. Sólo necesitamos usar una entrada de casilla de verificación con este marcado específico y estructura de clase CSS para que aparezcan como botones de palancas móviles.



Esta vista tiene un controlador declarado en settings.js, pero inyecta un servicio Types
que aún no hemos creado. Lo solucionaremos añadiendo un nuevo servicio a www/js/app.js.
1 |
.factory('Types', function() { |
2 |
return [ |
3 |
{type: 'Park', enabled: true}, |
4 |
{type: 'Hospital', enabled: true}, |
5 |
{type: 'Library', enabled: true}, |
6 |
{type: 'Museum', enabled: true} |
7 |
];
|
8 |
})
|
Este servicio tiene una variedad de tipos de lugar. Tiene una propiedad para el nombre de cada tipo de lugar y si está habilitado o deshabilitado. Utilizamos la propiedad 'enabled' en el botón de palanca ngModel
para rastrear el estado si ese tipo debe ser mostrado.
En este punto, puedes abrir el menú lateral y navegar hasta el enlace de configuración. Puedes ver las dos pestañas, preferencias y más. En la pestaña de preferencias, puedes activar o desactivar los tipos de lugar.
Si vas a la pestaña About, puedes seleccionar la licencia para ver cómo navega la aplicación hacia otra ruta dentro de la pestaña. Si cambias entre las preferencias y la pestaña About después de ver la licencia, puedes ver la pestaña que recuerda el estado de licencia, incluso después de que te hayas ido, demostrando la naturaleza con estado de estas pestañas.
El último paso de este tutorial es actualizar la vista de lugares para utilizar el servicio Types
y cargar sólo los tipos de lugares deseados y utilizar el servicio del historial para manejar cuándo volver a cargar o utilizar la memoria caché.
3. Almacenamiento en caché y uso del servicio del historial
De forma predeterminada, Ionic almacena en caché las últimas 10 vistas y las mantiene en la memoria. Muchas aplicaciones pueden no tener muchos estados, lo que significa que toda tu aplicación podría permanecer en la memoria. Esto es útil porque significa que Ionic no tiene que volver a mostrar la vista antes de navegar y esto acelera la aplicación.
Esto puede causar algunos problemas de comportamiento, ya que podrías pensar que tus estados siempre recargan y reinicializan el controlador en cualquier momento cuando se accede al estado. Dado que sólo se almacenan en caché 10 vistas, si tienes 20 vistas, sólo las últimas 10 estarán en la memoria caché. Esto significa que no puedes garantizar que una vista esté en caché o no. Por lo tanto, debes evitar realizar la lógica de configuración en tus controladores, fuera de los ganchos del ciclo de vida. También puedes configurar estrategias de almacenamiento en caché utilizando $ionicConfigProvider
.
A veces es necesario mirar el historial de navegación del usuario para determinar qué hacer. Por ejemplo, en esta aplicación, queremos mantener la lista de lugares en caché si el usuario teclea en un lugar y luego regresa a la lista. Si actualizamos automáticamente la lista en cada visita, los usuarios podrían perder su lugar en la lista después de haber recorrido y visto alguno.
Por otro lado, si un usuario navega a la página de configuración y luego regresa a la lista de lugares, queremos actualizar la lista, ya que pueden haber cambiado los tipos de lugares que desean mostrar.
Vamos a utilizar una combinación de los eventos de ciclo de vida que hemos visto antes con el servicio $ionicHistory
para agregar alguna lógica que ayudará a determinar cuándo el estado de lugares debe volver a cargar la lista. También queremos utilizar el servicio Types
para ayudarnos a cargar sólo los tipos de lugares que el usuario desea ver.
Abre www/views/places/places.js y actualízalo para que coincida con el siguiente código. Necesitamos cambiar la forma en que se cargan los datos y usar el servicio $ionicHistory
e inspeccionar el historial para determinar cuándo volver a cargar.
1 |
angular.module('App') |
2 |
.config(function($stateProvider) { |
3 |
$stateProvider.state('places', { |
4 |
url: '/places', |
5 |
controller: 'PlacesController as vm', |
6 |
templateUrl: 'views/places/places.html' |
7 |
});
|
8 |
})
|
9 |
.controller('PlacesController', function($http, $scope, $ionicLoading, $ionicHistory, Geolocation, Types) { |
10 |
var vm = this; |
11 |
var base = 'https://civinfo-apis.herokuapp.com/civic/places?location=' + Geolocation.geometry.location.lat + ',' + Geolocation.geometry.location.lng; |
12 |
var token = ''; |
13 |
vm.canLoad = true; |
14 |
vm.places = []; |
15 |
|
16 |
vm.load = function load() { |
17 |
$ionicLoading.show(); |
18 |
var url = base; |
19 |
var query = []; |
20 |
angular.forEach(Types, function(type) { |
21 |
if (type.enabled === true) { |
22 |
query.push(type.type.toLowerCase()); |
23 |
}
|
24 |
});
|
25 |
url += '&query=' + query.join('|'); |
26 |
|
27 |
if (token) { |
28 |
url += '&token=' + token; |
29 |
}
|
30 |
|
31 |
$http.get(url).then(function handleResponse(response) { |
32 |
vm.places = vm.places.concat(response.data.results); |
33 |
token = response.data.next_page_token; |
34 |
|
35 |
if (!response.data.next_page_token) { |
36 |
vm.canLoad = false; |
37 |
}
|
38 |
$scope.$broadcast('scroll.infiniteScrollComplete'); |
39 |
$ionicLoading.hide(); |
40 |
});
|
41 |
};
|
42 |
|
43 |
$scope.$on('$ionicView.beforeEnter', function() { |
44 |
var previous = $ionicHistory.forwardView(); |
45 |
if (!previous || previous.stateName != 'place') { |
46 |
token = ''; |
47 |
vm.canLoad = false; |
48 |
vm.places = []; |
49 |
vm.load(); |
50 |
}
|
51 |
});
|
52 |
});
|
Primero, hemos modificado la forma en que se construye la URL para que nuestra API cambie de cargar solo parques a cargar los tipos solicitados. Si comparas esto con la versión anterior, se utiliza principalmente angular.forEach
para realizar un bucle en cada tipo y añadirlo a la URL.
También hemos modificado el comportamiento del servicio $ionicLoading
. En lugar de ejecutar inmediatamente cuando el controlador se ejecuta inicialmente, lo activamos cada vez que se llame al método vm.load()
. Esto es importante porque el controlador se almacenará en caché y no recargará los datos de forma predeterminada.
El mayor cambio es el controlador de eventos $ionicView.beforeEnter
. Este evento se dispara antes de que la vista esté a punto de convertirse en la siguiente vista activa y nos permita hacer alguna configuración. Usamos el método $ionicHistory.forwardView()
para obtener información sobre la última vista en la que se encontraba el usuario.
Si es la primera carga, entonces ésta estará vacía, de lo contrario devolverá algunos datos sobre el último estado. A continuación, verificamos si el estado anterior era el estado de lugar y, de ser así, utilizamos la lista de resultados en caché. Además, como tenemos menos de 10 estados, sabemos que el estado siempre se guardará en la memoria.
De lo contrario, restablecerá los valores almacenados en caché y activará una nueva carga de datos. Esto significa que cada vez que vuelvo a la vista lugares después de ir a la configuración, se volverán a cargar los datos. Dependiendo del diseño de la aplicación, es probable que desees diseñar diferentes reglas condicionales sobre cómo manejar el almacenamiento en caché y la recarga.
El servicio de historial proporciona más información, como la pila completa del historial, la capacidad de modificar el historial, detalles sobre el estado actual y más. Puedes utilizar este servicio para ajustar la experiencia mientras navegas en la aplicación.
Vamos a hacer otros dos pequeños ajustes a nuestra plantilla de lugares. Abre www/views/places/places.html y cambia el título a Local Places.
1 |
<ion-view view-title="Local Places" hide-back-button="true"> |
Después, actualiza el componente de desplazamiento infinito con un atributo más, immediate-chek
, para evitar que el componente de desplazamiento infinito cargue los datos al mismo tiempo que la carga inicial. Esto esencialmente evita duplicar las solicitudes de más datos.
1 |
<ion-infinite-scroll on-infinite="vm.load()" ng-if="vm.canLoad" immediate-check="false"></ion-infinite-scroll> |
En este punto, hemos construido una aplicación bastante sólida que tiene un bonito conjunto de características. Terminaremos esta serie con un último tutorial mirando a Cordova e integrándonos con algunas de las características del dispositivo, como el acceso a datos GPS.
Conclusión
La navegación con Ionic comienza siempre con declarar algunos estados. Exponiendo que la navegación se puede hacer de varias maneras como hemos visto en este tutorial. Esto es lo que hemos cubierto:
- Los componentes de los menús laterales facilitan la creación de uno o dos menús laterales que se pueden activar bajo demanda o deslizándolo.
- Las pestañas pueden ser apátridas o de estado. Las pestañas de estado pueden tener vistas individuales con historiales de navegación por separado.
- Las pestañas tienen muchas opciones de configuración para ver cómo se muestran los iconos y el texto.
- Un botón de palanca es un componente CSS que funciona como una casilla de verificación, pero está diseñado para móviles.
- Puedes utilizar el servicio
$ionicHistory
para obtener más información sobre el historial de navegación de la aplicación y personalizar la experiencia.