ModusCreateOrg / ionic-vue

Vuejs integration for Ionic versions 4 and 5
MIT License
272 stars 26 forks source link

Different routers for each tab #103

Open lights0123 opened 4 years ago

lights0123 commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

@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 commented 4 years ago

@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 commented 4 years ago

@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:

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 commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

@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 commented 4 years ago

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

aileksandar commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

Are you saying you get the same error?

aileksandar commented 4 years ago

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

michaeltintiuc commented 4 years ago

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