Skip to content
Aprende a hacer apps móviles con Ionic 2 

Ionic DeepLinker II: URLs y Tabs

Este artículo es la continuación de Ionic DeepLinker I: Navegando por URLs. En el anterior expuse los conceptos básicos de la navegación por URLs en Ionic. Ahora te hablaré de cómo sortear los obstáculos que encontrarás al navegar por URL cuando utilizas Tabs.

DeepLinker con Tabs

Usar DeepLinker con Tabs genera una cierta confusión y actualmente comporta ciertas limitaciones.

¿Y no debería ser igual de simple? Sí y no. Es todo cuestión de arquitectura. Veamos a qué me refiero.

En el otro artículo te comentaba que el DeepLinker funciona sobre un NavController. Ésto te permite navegar al mismo nivel entre varias vistas, más o menos como ves en la imagen.

deepLinker Over IonNav

El lío de los NavControllers

Sin embargo los Tabs tienen su propio NavController interno. Si creas tu proyecto a partir del starter “tabs”, estarás cargando el NavController de los Tabs sobre otro NavController. ¡Debes tenerlo en cuenta si quieres usar el DeepLinker para navegar entre tabs!

La imagen siguiente muestra la estructura del starter tabs.

 deepLinker Tabs Starter

El problema con esta aproximación es que el DeepLinker trabaja con el NavController inicial, por lo que…

  • puede cargar la vista de tabs (siempre con el mismo tab activo por defecto)
  • puede cargar una vista particular de forma individual
  • puede cargar la vista de tabs, y luego, encima, un tab en particular
  • pero NO puede cargar directamente un tab en particular

El resumen sería el gráfico a continuación.

DeepLinker routing strategies with ion-nav and tabs

Como ves, no puedes navegar por URL directamente a un tab concreto.

-Es un poco confuso… ¿Me pones un ejemplo?
-Sí, claro, como no.

Deeplinker con el starter tabs

Inicia un proyecto nuevo basado en el starter tabs:

ionic start example tabs --v2

Actualiza el NgModule de src/app/app.module.ts para añadir la configuración del DeepLinker:

//src/app/app.module.ts
//...some stuff...

@NgModule({
  //...more stuff...
  imports: [
    IonicModule.forRoot(MyApp, {}, {
      links:[
        {segment:'home', component:HomePage, name: 'Home Page'},
        {segment:'contact', component:ContactPage, name: 'Contact Page'},
        {segment:'about', component:AboutPage, name: 'About Page'}
      ]
    })
  ],
  //...more stuff...
})
export class AppModule {}

Hecho. Seguramente esperas que al escribir la URL [baseUrl]/about te cargue la vista About y veas los tabs debajo ¿verdad?

Pues no.

Lo que obtienes es lo de la imagen:

DeepLinker With Tabs First Attempt

Ignora de momento las flechas rojas.

A la izquierda tienes la navegación normal en la app. Ves los tabs y en este caso estas en el tab Home. El DeepLinker ha actualizado la URL, pero no es lo que esperabas, sino [baseUrl]/home/home.

A la derecha tienes la navegación si introduces directamente la URL. El DeepLinker no reconoce la URL anterior, pero sí la URL del tipo [baseUrl]/home, aunque… ¡ojo! Te carga la vista sin tabs.

Y esta ese otro tema, las flechas rojas. Parece que la navegación in-app duplica la ruta… ¿no?

Te cuento…

Segments VS TabUrlPath

Como decía, el componente <ion-tabs> incorpora su propio NavController. Esto a su vez le permite gestionar sus propias rutas y lo hace a su manera.

La ruta a un tab la puedes definir con el atributo tabUrlPath, como ves a continuación.

<!-- example of tabUrlPath -->
<ion-tabs>
  <ion-tab [root]="tab1Root" tabUrlPath='home' tabTitle="Home"></ion-tab>
  <ion-tab [root]="tab2Root" tabUrlPath='about' tabTitle="About"></ion-tab>
</ion-tabs>

Como el DeepLinker te fuerza a usar URLs, aunque no las pongas tú con tabUrlPath, tus tabs las generan igualmente a partir del atributo tabTitle.

Volviendo a la imagen del ejemplo, al usar el DeepLinker con tabs, la navegación acaba añadiendo la URL del tab y la del segment. Lo tienes en la captura de la izquierda.

DeepLinker With Tabs First Attempt

No obstante, al refrescar la URL, el DeepLinker usa el NavController inicial (no el de los tabs). Por eso carga la página sin tabs, y por eso la URL no refleja el tabUrlPath, sino solo el segment (captura de la derecha).

Solucionando rutas duplicadas, primer intento

Puedes hacer un apaño para intentarlo arreglar, forzando que los tabUrlPath sean un string vacío:

<!-- example of tabUrlPath -->
<ion-tabs>
  <ion-tab [root]="tab1Root" tabUrlPath='' tabTitle="Home"></ion-tab>
  <ion-tab [root]="tab2Root" tabUrlPath='' tabTitle="About"></ion-tab>
</ion-tabs>

Pero la URL pasará a ser tipo [baseUrl]//home. Aparece una doble barra (//). Además, si navegas por URL no funciona la doble barra y sigue sin cargar los tabs.

¿Y al revés?

Solucionando rutas duplicadas, segundo intento

Puedes optar por usar segments vacíos y dejar la navegación en manos de los tabUrlPath:

//src/app/app.module.ts

//...inside @NgModule...

      links:[
        {segment:'', component:HomePage, name: 'Home Page'},
        {segment:'', component:ContactPage, name: 'Contact Page'},
        {segment:'', component:AboutPage, name: 'About Page'}
      ]

¡Casi!

Ya has conseguido la URL que querías [baseUrl]/home.

Pero hay un pequeño problema. Si intentas navegar por URL a un tab concreto (por ejemplo [baseUrl]/contacts), te redirige a la vista que trae activado el tab por defecto (en este caso [baseUrl]/home).

Ya te lo he dicho. Con 2 NavControllers anidados, el DeepLinker no puede cargar directamente un tab en concreto.

Este “directamente” es la clave. La idea de los segments es que sean justamente eso, segmentos de la URL, no URLs completas: los segmentos pueden encadenarse…

URL a un tab concreto con el starter tabs

Para acceder por URL a un tab concreto usando esta estructura de doble NavController, necesitas un segmento adicional para la propia vista de tabs.

De este modo, tu URL puede incluir los segmentos de ambos componentes (la vista de tabs y el contenido del tab), y conseguir cargarlos. La URL quedaría así [baseUrl]/mytabs/specifictab.

Es el último caso que te mostraba en esta imagen:

DeepLinker routing strategies with ion-nav and tabs

Lo bueno es que casi tenías la solución. Continua con el código anterior y añade un nuevo segmento para el componente TabsPage:

//src/app/app.module.ts

//...inside @NgModule...
      links:[
        {segment:'tabs', component:TabsPage, name: 'Tabs Page'},
        {segment:'', component:HomePage, name: 'Home Page'},
        {segment:'', component:ContactPage, name: 'Contact Page'},
        {segment:'', component:AboutPage, name: 'About Page'}
      ]

Fíjate en el resultado. La URL que te aparece es del estilo [baseUrl]/tabs/home.

DeepLinker with tabs starter

Además, si accedes directamente a través de la URL, verás que ahora SI te carga el tab adecuado y la URL coincide con la que obtienes navegando a través de la app.

Como los segments pueden encadenarse, con la estructura adecuada el DeepLinker cargará la vista de tabs y el contenido del tab concreto que deseas.

¿Y si NO quieres que tu ruta tenga este segmento extra delante?

¿Te parece un pegote?

¿Quieres una URL más simple?

No te desesperes, tiene solución, todo es cuestión de arquitectura.

Todo es cuestión de arquitectura

El starter tabs introduce una estructura ciertamente compleja al usar los tabs dentro de un componente <ion-nav>.

Se me ocurren ciertos casos donde la navegación fuera de los tabs puede interesarte (vista de login, walkthrough, …), pero no siempre es necesario.

Si no es tu caso, puedes solucionarlo de forma muy sencilla: Simplifica la arquitectura.

Si no tienes los tabs dentro de un <ion-nav>, no vas a tener ese doble NavController y podrás navegar directamente al tab que quieras.

Eso sí, con unas pequeñas precauciones.

DeepLinker con Tabs y sin ion-nav

Puedes partir directamente del starter tabs.

Edita src/app/app.component.html para que coincida con el contenido de tabs.html, es decir:

<!-- src/app/app.html -->
<ion-tabs>
  <ion-tab [root]="tab1Root" tabTitle="Home" tabIcon="home"></ion-tab>
  <ion-tab [root]="tab2Root" tabTitle="About" tabIcon="information-circle"></ion-tab>
  <ion-tab [root]="tab3Root" tabTitle="Contact" tabIcon="contacts"></ion-tab>
</ion-tabs>

Elimina las referencias a TabsPage de src/app/app.component.ts y añade las rootPages de cada tab como hacía el componente TabPage:

// src/app/app.component.ts
import { Component } from '@angular/core';
import { Platform } from 'ionic-angular';
import { StatusBar, Splashscreen } from 'ionic-native';

import { HomePage } from '../pages/home/home';
import { AboutPage } from '../pages/about/about';
import { ContactPage } from '../pages/contact/contact';


@Component({
  templateUrl: 'app.html'
})
export class MyApp {

  tab1Root: any = HomePage;
  tab2Root: any = AboutPage;
  tab3Root: any = ContactPage;  

  constructor(platform: Platform) {
    platform.ready().then(() => {
      // Okay, so the platform is ready and our plugins are available.
      // Here you can do any higher level native things you might need.
      StatusBar.styleDefault();
      Splashscreen.hide();
    });
  }
}

Elimina la carpeta src/pages/tabs, ya no la necesitas.

Finalmente, elimina todas las referencias a TabsPage del archivo src/app/app.module.ts.

Además, añádele la configuración del DeepLinker con segmentos vacíos a cada uno de los tabs:

// src/app/app.module.ts

import { NgModule, ErrorHandler } from '@angular/core';
import { IonicApp, IonicModule, IonicErrorHandler } from 'ionic-angular';
import { MyApp } from './app.component';
import { AboutPage } from '../pages/about/about';
import { ContactPage } from '../pages/contact/contact';
import { HomePage } from '../pages/home/home';

@NgModule({
  declarations: [
    MyApp,
    AboutPage,
    ContactPage,
    HomePage,
  ],
  imports: [
    IonicModule.forRoot(MyApp, {}, {
      links:[
        {segment:'', component:HomePage, name: 'Home Page'},
        {segment:'', component:ContactPage, name: 'Contact Page'},
        {segment:'', component:AboutPage, name: 'About Page'}
      ]
    })
  ],
  bootstrap: [IonicApp],
  entryComponents: [
    MyApp,
    AboutPage,
    ContactPage,
    HomePage,
  ],
  providers: [{provide: ErrorHandler, useClass: IonicErrorHandler}]
})
export class AppModule {}

Voilà!

Si ejecutas el servidor de desarrollo verás como por fín las URL siguen el formato [baseUrl]/home.

Además, si refrescas en una URL concreta verás que te carga el tab que corresponde. Sin cambiar la URL ¡¡¡YEAH!!

Puedes verlo en acción en el siguiente vídeo.

 
Te gusta ¿eh?

Te dejo el repositorio con el código del ejemplo:

Notas finales

A lo largo de este POST has visto varias formas de navegar por URLs usando Tabs en ionic.

Personalmente me gusta más esta última aproximación, donde las URLs son más claras y la estructura más simple. Eso sí, hay situaciones en las que necesitarás una navegación más compleja como la que te explicaba al principio.

El DeepLinker es una solución muy potente a un problema que no es trivial. Si de la forma correcta te permite hacer casi todo lo que quieras, aunque aún tiene margen de mejora para ser más flexible.

A mi, personalmente, se me hace extraño pasar segmentos vacíos al DeepLinkerConfig y dejar la navegación por URLs en manos de los tabUrlPaths. Creo que el caso de los Tabs merece algún campo adicional en el DeepLinkerConfig que simplifique estos temas.

¿Qué aproximación te gusta más a ti? ¿Te ha convencido el DeepLinker? ¿Lo utilizas?

¡Un abrazo!

Published inionicIonic 2PhoneGap

2 Comments

  1. seria un buen turorial sobre la mejor forma de tener constantes en el proyecto de configuracion por ejemplo, variables que cambien segun entornos y esas cosas

    saludos

  2. Javi Javi

    ¿Sería posible navegar a una pagina de la app a través de un enlace html en un texto? Estoy haciendo una app que carga textos desde un webservice y necesito que algunas palabras de ese texto sean enlaces a otra pagina de la app y hasta ahora no he encontrado una solución.

Deja un comentario