Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Different routers for each tab #103

Open
lights0123 opened this issue Dec 22, 2019 · 16 comments
Open

Different routers for each tab #103

lights0123 opened this issue Dec 22, 2019 · 16 comments

Comments

@lights0123
Copy link

I'm writing a mobile app. Each tab should have its own navigation stack, i.e. you can navigate in the "News" tab, switch to the about tab and hit "About", and switch back to the News tab, leaving off right where you were, in both scroll position and path. In my app, I'm using version 1.1.2 of @modus/ionic-vue, as it does not require a router for tabs. The way I have this working is that each tab has its own VueRouter running in abstract mode. This shouldn't be possible in other navigation modes, as going back should not change tabs. Other apps, such as the ionic-vue-conference-app, try to implement this, but fail badly. Other than being broken by using relative paths, switching between tabs doesn't retain the previous navigated path. This is a key feature in many apps, and is holding me back to this old version. See this demo of what I currently have, which is what I want. What is the proper way to do this?

@mattsteve
Copy link

mattsteve commented Jan 7, 2020

Since they just made the fix so you can add a custom event to handle tab buttons (not yet published to npm), you can do this manually, assuming named views still work with ion-vue-router.

Vue Router Named Views:
https://router.vuejs.org/guide/essentials/named-views.html

<template>
    <ion-tabs>
        <ion-vue-router v-show="selectedTab === 'tab1'" name="tab1router" />
        <ion-vue-router v-show="selectedTab === 'tab2'" name="tab2router" />

        <ion-tab-bar slot="bottom">
            <ion-tab-button
                :selected="selectedTab === 'tab1'"
                @click="selectedTab = 'tab1"
            >
                Tab 1
            </ion-tab-button>
            <ion-tab-button
                :selected="selectedTab === 'tab2'"
                @click="selectedTab = 'tab2"
            >
                Tab 2
            </ion-tab-button>
        </ion-tab-bar>
    </ion-tabs>
</template>

<script>
export default {
    data() {
        return {
            selectedTab: 'tab1'
        }
    }
};
</script>

Router config

const router = new VueRouter({
    routes: [
        {
            path: '/',
            components: {
                default: Foo,
                tab1router: Tab1Component,
                tab2router: Tab2Component
            }
        }
    ]
})

@lights0123
Copy link
Author

@mattsteve the problem with this is that the history is shared between the two tabs—it is not possible to get away with only one router. Changing the page on the first tab will change the page on the second, which is certainly not what you want. I ended up figuring out that you can still do what I did, and nest routers. However, I ended up switching to Ionic's ion-nav, so I get native-like swipe-to-go-back behavior on iOS. I used vue-custom-element and vue-fragment to make that work.

@michaeltintiuc
Copy link
Member

@lights0123 thanks for sharing your implementation, sorry for not replying earlier. Could you share your implementation using ion-nav? I am working on the tabs functionality for the Vue 3 version and was considering a router with a stack of history states or something along those lines and I was thinking of back-porting it to the upcoming 2.0.0 version as well. I just don't want to force people into using a custom router, I'd rather have them work with something they're accustom to

@lights0123
Copy link
Author

lights0123 commented Apr 25, 2020

@michaeltintiuc Here's the complete project. I actually removed all Ionic-Vue parts, and I'm now just using Ionic components directly from Vue because I got a bunch of errors when migrating to Ionic 5. This commit is the last commit to use this repository, on Ionic 4. Here's the main layout of my app before I switched to using my own bindings:

One central component for the main app:

<template>
	<ion-app>
		<ion-tabs>
			<ion-tab tab="news">
				<ion-nav root="app-news" />
			</ion-tab>

			<ion-tab tab="saved">
				<ion-nav root="app-saved" />
			</ion-tab>

			<ion-tab tab="settings">
				<ion-nav root="app-settings" />
			</ion-tab>

			<ion-tab-bar slot="bottom" ref="tabBar">
				<ion-tab-button tab="news" @click="click('news')">
					<ion-icon name="paper" />
					<ion-label>News</ion-label>
				</ion-tab-button>

				<ion-tab-button tab="saved" @click="click('saved')">
					<ion-icon :src="bookmarkURL" class="bookmark-icon" />
					<ion-label>Saved</ion-label>
				</ion-tab-button>

				<ion-tab-button tab="settings" @click="click('settings')">
					<ion-icon name="settings" />
					<ion-label>Settings</ion-label>
				</ion-tab-button>
			</ion-tab-bar>
		</ion-tabs>
	</ion-app>
</template>

A router:

/* Note: I'm implicitly letting there be more than this route, as they are automatically pushed by <ion-tabs>, and Ionic takes care of displaying different components */
/* This is the only Vue router in the entire project, everything else is done through Ionic APIs */
/* This was removed in my latest version, where I switch away from using this repo */
router: new VueRouter({
	mode: 'history',
	base: process.env.BASE_URL,
	routes: [{ path: '/', redirect: '/news' }],
}),

Each page has:

<template>
	<!-- <fragment> seems to be needed when using this as a component, placing the header and content in a <div> broke stuff -->
	<!-- Vue 3 is getting multi-component roots (I think, right?) -->
	<fragment>
		<ion-header>
			<ion-toolbar>
				<ion-title><logo /></ion-title>
			</ion-toolbar>
		</ion-header>
		<ion-content>
			whatever
		</ion-content>
	</fragment>
</template>
<script lang="ts">
/* Hack to make each component be a child of the main Vue instance so things like Vuex
 * are available from each component, and makes debugging via the Vue Devtools plugin
 * possible
 *
 * Very clunky, I'd like to get rid of it but it works 🥴 (except hot-reload breaks half the time,
 * but stable in production)
 */
export const injectParent = {
	beforeCreateVueInstance(RootComponentDefinition) {
		RootComponentDefinition.parent = window.vue;
		RootComponentDefinition.store = window.vue.$store;
		return RootComponentDefinition;
	},
};
/* I'm using vue-class-component, and `News` is the name of my default export 
 * This will likely be different (namely not including `.options`) if you're using the standard
 * `export default`, and I have no clue what you would do with the new Composition API
 *
 * Note how this matches up with the `root` attribute of each <ion-nav />
 */
Vue.customElement('app-news', (News as any).options, injectParent);
</script>

Once, to initialize that:

import Vue from 'vue';
import vueCustomElement from 'vue-custom-element';
import { Plugin } from 'vue-fragment';
import Vue from 'vue';

Vue.use(vueCustomElement);

This setup has a few advantages:

  • Different routers for each tab, so you can actually use it like a standard app with different page histories in each tab
  • Saves scroll position without hacks when going back to a previous page, as <ion-nav> basically does the same thing as <keep-alive>
  • Feels native on iOS, so you can swipe back like a standard iOS app, and the page follows your finger without the weird thing implemented by a few Ionic-Vue apps that just goes back when you swipe 50% across the screen. It also allows you to decide while you're swiping that you don't want to go back, and stay on the current page

I understand that you probably don't want to make everyone follow these same steps, which is why I've basically migrated away from this repo (as it was getting in the way with in Ionic 5). However, the lack of native iOS feel and this issue was a dealbreaker for me.

@michaeltintiuc
Copy link
Member

Thanks for the example and feedback @lights0123 I think most of this stuff makes a lot of sense and is the general direction v3 is going in. Would be interested in hearing your thoughts on the new version when it's out of alpha

@michaeltintiuc
Copy link
Member

You can give a try with the vue 3 version, npm i @modus/ionic-vue@next it's not separate routers per tab as that's not quite possible with Vue or even web history as the end of the day, but the behavior is much closer to what is expected

@aileksandar
Copy link

I'm trying to test out the tabs functionality with Vue 3 and ionic-vue@next, and I'm stuck.

<template>
  <IonApp>
    <IonTabs>
      <IonTab tab="tab1" :routes="['home']" :to="{name: 'home'}">
        <IonRouterView name="tab1"/>
      </IonTab>
      <IonTab tab="tab2" :routes="['about']" :to="{name: 'about'}">
        <IonRouterView name="tab2" />
      </IonTab>
      <template v-slot:bottom>
        <IonTabBar>
          <IonTabButton tab="tab1" :to="{name: 'home'}">
            <IonLabel>Tab 1</IonLabel>
          </IonTabButton>
          <IonTabButton tab="tab2" :to="{name: 'about'}">
            <IonLabel>Tab 2</IonLabel>
          </IonTabButton>
        </IonTabBar>
      </template>
    </IonTabs>
  </IonApp>
</template>

<script>
import {
  IonApp,
  IonTab,
  IonTabs,
  IonLabel,
  IonTabBar,
  IonTabButton,
  IonRouterView
} from "@modus/ionic-vue";

export default {
  name: "App",
  components: {
    IonApp,
    IonTab,
    IonTabs,
    IonLabel,
    IonTabBar,
    IonTabButton,
    IonRouterView
  },
};
</script>

I've made a setup like its described in the https://ionicframework.com/docs/api/tabs , just adapted to ionic-vue components, and I keep getting these errors.

Screenshot 2020-08-07 at 14 32 06

Can you give me some working example or point me at some direction or resource so I can figure it out? 🙏

Thanks

@michaeltintiuc
Copy link
Member

Sure, here's my setup with a router:

<template>
  <div class="ion-page">
    <IonTabs @ionTabsWillChange="myMethod">
      <RouterView />
      <template v-slot:top>
        <IonTabBar>
          <IonTabButton tab="foo" href="/">
            <IonIcon icon="add" />
          </IonTabButton>
          <IonTabButton tab="bar" href="/bar">
            <IonIcon icon="map" />
          </IonTabButton>
        </IonTabBar>
      </template>
    </IonTabs>
  </div>
</template>

Each component rendered by the router is similar to this:

<template>
  <IonTab tab="foo">
    <IonRouterView />
  </IonTab>
</template>

In this example each tab has sub-routes for navigation, meaning that in order to resolve IonRouterView we'd be using the router's nested routes defined by the children prop:

  routes: [
    {
      path: '/',
      component: foo,
      children: [
        {
          path: '',
          component: fooList,
        },
        {
          path: 'foo/:fooId',
          component: fooItem,
        },
      ],
    },

Note that we're using RouterView within IonTabs as we don't need any transitions and just want a route switch, within IonTab we're using IonRouterView to leverage transitions.

@aileksandar
Copy link

Thanks a lot. You've pointed me in the right direction with the multiple nested children routes. To be honest it seems a little odd that you have 2 levels of children routes required to have tabbed routing, but I guess it makes sense because root tab routes should not have animation.

I have one more issue, and I think it's related to tabs. ios swipe back gesture doesn't work, it just freezes, and the tab bar disappears. It's strange that I'm not seeing any console error.

I've pushed the code here so you can reproduce and give me a hand if you can

https://github.com/meoweloper/ionic-vue-3

@michaeltintiuc
Copy link
Member

@meoweloper Please see this PR against your repo https://github.com/meoweloper/ionic-vue-3/pull/1
The tabs work, but you pointed out a type-o in the router outlet, I'm going to release a new version ASAP, thank you!
Please note that swipe-back currently does not have a limit to itself, meaning that you can swipe back past the tab's root route, the fix is in the works, the router will have separate history states for each tab and one for global navigation

@michaeltintiuc
Copy link
Member

See version 3.0.0-alpha.14 should be available in a bit

@aileksandar
Copy link

Glad to hear that separate history is in the works, I think thats the missing piece that will push tabs to a beta state.

@aileksandar
Copy link

See version 3.0.0-alpha.14 should be available in a bit

I found that same issue component type, just didn't know how to fix it 👶 ⌨️

@michaeltintiuc
Copy link
Member

Are you saying you get the same error?

@aileksandar
Copy link

No, you fixed it in alpha.14. Just saying I failed to fix it myself.

@michaeltintiuc
Copy link
Member

It was a library issue that I introduced some time ago, it's on me :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants