Router de Ionic

<< Capacitor Ejemplo productos >>

Al utilizar la integración de Ionic con Angular, Ionic utiliza para gestionar la navegación entre páginas el router de Angular con una capa de abstracción extra. Esto añade funcionalidades como la animación entre cambio de páginas y la apilación de páginas (stacking), que explicaremos a continuación.

Crear páginas

En Ionic (con Angular), las páginas de la app son componentes Angular. La única diferencia con una aplicación Angular estándar es que podemos usar el siguiente comando para crear una página por ejemplo:

ionic g page home

Esto creará un componente que representará la página home de nuestra aplicación. La diferencia con crear un componente, es que nos habrá creado un esqueleto de página con encabezado y contenido. Por lo demás, es un componente Angular típico.

Se pueden eliminar importaciones innecesarias ya que no se utilizan en este momento como CommonModule o FormsModule, así como también el atributo standalone: true (por defecto en Angular 19) y métodos como el constructor o ngOnInit.

<ion-header [translucent]="true">
  <ion-toolbar>
    <ion-title>home</ion-title>
  </ion-toolbar>
</ion-header>

<ion-content [fullscreen]="true">
  <ion-header collapse="condense">
    <ion-toolbar>
      <ion-title size="large">home</ion-title>
    </ion-toolbar>
  </ion-header>
</ion-content>

Vamos a explicar algunas cosas del HTML generado:

  • El primer ion-header es la barra superior que contiene el título de la página. La propiedad translucent solo funciona en iOS y cuando está a true hace un efecto de semitransparencia con el contenido que pasa por debajo.
  • El elemento ion-content representa al contenido de la página. Si ocupa más que el alto disponible, es el contenido sobre el que hacemos scroll quedando el encabezado fijo.
  • El atributo fullscreen a true indica que el contenido pasa por detrás del encabezado y pie. Este solo será visible en todo caso si el encabezado tiene algún tipo de transparencia (opacity) o tiene la propiedad translucent a true (solo iOS).
  • El segundo ion-header solo funciona en iOS y si está presente, cuando estemos al principio de la página, la barra superior desaparece y se mostrará este encabezado en su lugar. Al hacer scroll hacia abajo, la barra del encabezado superior aparece en su lugar.
  • También se puede añadir un pie de página (ion-footer).

Rutas de la aplicación

Cuando generamos una página, además del esqueleto inicial, nos añadirá dicha página al archivo de rutas de la aplicación (app.routes.ts):

export const routes: Routes = [
  //...
  {
    path: 'home',
    loadComponent: () => import('./home/home.page').then( m => m.HomePage)
  },
];

Podemos observar que por defecto, todas los componentes de las rutas se cargan con Lazy Loading por defecto. En aplicaciones más complejas podemos subdividir las rutas en varios archivos, cada archivo asociado a un prefijo, al igual que cualquier aplicación hecha con Angular.

Ionic utiliza el router de Angular por debajo, pero añade una serie de funcionalidades a la navegación entre páginas.

Cuando navegamos hacía adelante (por defecto) a otra página, además de añadir una animación específica en el cambio de página, "apila" la nueva página encima de la existente. Es decir, no elimina la página (componente) anterior del DOM para cargar la nueva.

Esto quiere decir, que además de tener algún tipo de animación para dar la impresión de que no siempre navegamos hacía adelante, también querremos "desapilar" páginas en algún momento (volver atrás), o sustituir toda la pila de páginas por una página nueva (root). Si no, al final tendríamos todas las páginas de la aplicación cargadas a la vez, aunque solo 1 estuviera visible.

Para crear un enlace en la plantilla utilizamos, como siempre en una aplicación Angular, la directiva [routerLink]. En Ionic le añadiremos también el parámetro de entrada routerDirection, que determina el sentido de la navegación y puede tomar los siguientes valores:

  • forward → Navega hacía adelante, con la animación correspondiente y apilando la nueva página encima de la actual.
  • back → Navega hacia atrás, con la animación correspondiente. En este caso busca la página en la pila y desapila todas las páginas que hubiera encima. Si no encuentra la página, se comporta como la opción root pero con animación.
  • root → Elimina toda la pila de páginas y crea una nueva pila con la página destino como primera página o raíz. Esta transición es directa, sin animación.

Aunque el parámetro routerDirection está presente en los componentes de Ionic (ion-button, ion-item, ...), no funcionará si no importamos la directiva IonRouterLink en el componente. En el caso de tener enlaces (<a>) con routerDirection, debemos importar IonRouterLinkWithHref.

<!-- ... -->
<ion-item [routerLink]="url" routerDirection="forward">
  <ion-label>
    {{urlName}}
  </ion-label>
</ion-item>
<!-- ... -->
<a [routerLink]="['/home']" routerDirection="back">Home</a>
<!-- ... -->

La navegación desde código se puede realizar con el router de Angular. Sin embargo, si queremos utilizar la navegación hacía adelante o hacía atrás, necesitaremos el servicio NavController de Ionic. Este servicio tiene los siguientes métodos navigateForward, navigateBack y navegateRoot que añaden el comportamiento que hemos descrito antes a la navegación en Ionic.

//...
export class MyPage{
  #nav = inject(NavController);

  goNavForward() {
    this.#nav.navigateForward(['/route']); // Animación hacia adelante (y apilar)
  }

  goNavBack() {
    this.#nav.navigateBack(['/route']); // Animación hacia atrás (y desapila)
  }

  goNavRoot() {
    this.#nav.navigateRoot(['/route']); // Sin animación (sustituye pila entera)
  }
}

Ciclo de vida de las páginas

Como componentes de Angular, las páginas de Ionic pueden utilizar los mismos eventos de ciclo de vida que en cualquier aplicacion Angular (ngOnInit, ngOnDestroy, etc.). Sin embargo, debido al sistema de navegación de Ionic, pueden ocurrir 2 situaciones en las que estos eventos no sean suficientes:

  • Al navegar hacia adelante, la página actual no se destruye, por lo que no se ejecutará ngOnDestroy o la cancelación de observables (takeUntilDestroyed) si lo necesitáramos.
  • Al navegar hacia atrás, la página anterior ya estaría cargada en memoria, por lo que Angular no tendría que volver a crear el componente. Por ello, no se ejecuta el constructor ni el método ngOnInit. En el caso en el que queramos recargar la información al volver a una página anterior, esto podría ser un problema.

Ionic introduce nuevos métodos para el ciclo de vida de las páginas para solventar estas nuevas situaciones:

  • ionViewWillEnter → Se ejecuta justo después de ngOnInit (si está presente) y cada vez que entramos en la página (aunque ya esté cargada en memoria). Es un buen método para la carga incial de datos. Sin embargo, este método se ejecuta justo antes de la animación de transición a la nueva página. Si se cargan datos que modifican mucho el DOM durante la animación, puede afectar a esta. En ese caso, sería mejor considerar el método ionViewDidEnter.
  • ionViewDidEnter → Se ejecuta después de ionViewWillEnter, concretamente después de que la animación de transición a la nueva página haya terminado.
  • IonViewWillLeave → Se ejecuta cada vez que vayamos a cargar otra ruta diferente a la actual, antes de que empiece la animación de transición. Es un buen método para hacer limpieza (cancelar subscripciones a observables, etc.).
  • ionViewDidLeave → Se ejecuta después de IonViewWillLeave, concretamente después de la animación de transición, por si queremos hacer algo una vez la página ya no esté visible. Si la página se elimina del DOM, el componente sería destruído y en ese caso, justo después, se ejecutaría ngOnDestroy si está presente.

Si necesitamos una aplicación con un menú lateral, este menú se definirá en la plantilla de AppComponent dentro de un elemento Menú (ion-menu). Un menú tiene su encabezado y contenido. Y generalmente, el contenido es una lista con las páginas a las que puedes navegar directamente.

Vamos a modificar ligeramente el menú que se crea por defecto quitando la sección labels. Quitaremos además el encabezado de la lista de enlaces añadiendo un encabezado de página al menú (ion-header). También eliminaremos las importaciones que no hagan falta, entre otras cosas los iconos que ya no son necesarios.

<ion-app>
  <ion-split-pane contentId="main-content">
    <ion-menu contentId="main-content" type="overlay">
      <ion-header>
        <ion-toolbar color="tertiary">
          <ion-title>Menu</ion-title>
        </ion-toolbar>
      </ion-header>
      <ion-content>
        <ion-list id="inbox-list">
          @for (p of appPages; track p; let i = $index) {
            <ion-menu-toggle auto-hide="false">
              <ion-item routerDirection="root" [routerLink]="[p.url]" lines="none" detail="false" routerLinkActive="selected">
                <ion-icon aria-hidden="true" slot="start" [ios]="p.icon + '-outline'" [md]="p.icon + '-sharp'"></ion-icon>
                <ion-label>{{ p.title }}</ion-label>
              </ion-item>
            </ion-menu-toggle>
          }
        </ion-list>
      </ion-content>
    </ion-menu>
    <ion-router-outlet id="main-content"></ion-router-outlet>
  </ion-split-pane>
</ion-app>

Hay que actualizar también las rutas de la aplicación para que nos lleven por defecto a la ruta /home.

export const routes: Routes = [
  {
    path: '',
    redirectTo: 'home',
    pathMatch: 'full',
  },
  {
    path: 'home',
    loadComponent: () => import('./home/home.page').then((m) => m.HomePage),
  },
  {
    path: '**',
    redirectTo: 'home',
  },
];

Botón de menú

En este tipo de navegación, las páginas principales, es decir, las que (normalmente) estarán accesibles directamente desde el menú, tendrán un botón en la barra de encabezado para mostrar el menú cuando esté oculto. Este botón está definido en el componente ion-menu-button.

<ion-header [translucent]="true">
  <ion-toolbar color="primary">
    <ion-buttons slot="start">
      <ion-menu-button></ion-menu-button>
    </ion-buttons>
    <ion-title>Home</ion-title>
  </ion-toolbar>
</ion-header>
<!-- ... -->

Botón de navegación hacia atrás

En las páginas no principales, suele ser recomendable poner un botón para volver a la página anterior en la barra de encabezado. Este botón está definido en el componente ion-back-button. También es recomendable añadir el parámetro defaultHref que permite establecer una ruta por defecto, cuando no hay páginas anteriores apiladas (por ejemplo, hemos cargado directamente la ruta con dicha página en el navegador). Si no usamos ese parámetro y no hay páginas anteriores, no se mostrará el botón.

Se pueden crear aplicaciones en las que la navegación principal sea por pestaña (tabs) en luagr de usar un menú lateral. Esto puede ser interesante para pequeñas apps que tengan un máximo de 3 o 4 páginas principales.

También existe la posibilidad dentro de una app con menú lateral de crear una página con navegación por pestañas que a su vez cargue subpáginas dentro de la misma. Vamos a imaginar el siguiente escenario:

  • Página para ver el detalle de un producto (product-detail). Esta página a su vez tendrá pestañas para acceder a 2 subpáginas que serán rutas internas dentro de la página principal.
  • La página product-info mostrará la información del producto
  • La página product-comments mostrará los comentarios de los usuarios

Este tipo de navegación lo vamos a desarrollar con detalle en la siguiente sección "Ejemplo de productos".

<< Capacitor Ejemplo productos >>