0

Is it possible to use Vue Router to create internal navigation for components added via a for loop?

  ┌────────────────────────────────────────────────────────────────┐
  │                           Application                          │
  │                                                                │
  │  ┌──────────────────────────────────────────────────────────┐  │
  │  │                          Router                          │  │
  │  │                                                          │  │
  │  │  ┌────────────────────────────────────────────────────┐  │  │
  │  │  │                        View                        │  │  │
  │  │  │                                                    │  │  │
  │  │  │  ┌──────────────────────────────────────────────┐  │  │  │
  │  │  │  │        v-for component in components         │  │  │  │
  │  │  │  │                                              │  │  │  │
  │  │  │  │  ┌──────────────────┐  ┌──────────────────┐  │  │  │  │
  │  │  │  │  │    Component     │  │    Component     │  │  │  │  │
  │  │  │  │  │ ┌──────────────┐ │  │ ┌──────────────┐ │  │  │  │  │
  │  │  │  │  │ │    Router    │ │  │ │    Router    │ │  │  │  │  │
  │  │  │  │  │ │ ┌──────────┐ │ │  │ │ ┌──────────┐ │ │  │  │  │  │
  │  │  │  │  │ │ │   View   │ │ │  │ │ │   View   │ │ │  │  │  │  │
  │  │  │  │  │ │ │          │ │ │  │ │ │          │ │ │  │  │  │  │
  │  │  │  │  │ │ │          │ │ │  │ │ │          │ │ │  │  │  │  │
  │  │  │  │  │ │ │          │ │ │  │ │ │          │ │ │  │  │  │  │
  │  │  │  │  │ │ └──────────┘ │ │  │ │ └──────────┘ │ │  │  │  │  │
  │  │  │  │  │ │              │ │  │ │              │ │  │  │  │  │
  │  │  │  │  │ └──────────────┘ │  │ └──────────────┘ │  │  │  │  │
  │  │  │  │  │                  │  │                  │  │  │  │  │
  │  │  │  │  └──────────────────┘  └──────────────────┘  │  │  │  │
  │  │  │  │                                              │  │  │  │
  │  │  │  └──────────────────────────────────────────────┘  │  │  │
  │  │  │                                                    │  │  │
  │  │  └────────────────────────────────────────────────────┘  │  │
  │  │                                                          │  │
  │  └──────────────────────────────────────────────────────────┘  │
  │                                                                │
  └────────────────────────────────────────────────────────────────┘

I could use an internal state variable and a bunch of v-if statements to show the desired view but it would be nice to be able to define internal routes for the components to control the view state with vue-router.

I've tried adding a new router with createRouter() but I can't figure out how I could use the router for the component (like createApp().use(mainRouter);)

Application view from main Router

<template>
    <section class="home">
        <car-panel v-for="item in itemList" 
                   :id="item.id"
                   />
    </section>
</template>

<script>
import { ref, onMounted } from 'vue';
import Panel from '@/components/Panel.vue';

export default {
    components: { Panel },
    setup() {
        const itemList = ref([]);

        onMounted(() => {
            // insert a random number of items above 1
            for (var i = 0; i < Math.floor(Math.random() * 5) + 1; i++) {
                itemList.value.push({
                    id: i.toString()
                });
            }
        });

        return {
            itemList,
        };
    },
}
</script>

Panel

<template>
    <article class="panel">
        <panel-nav></panel-nav>
        <router-view></router-view>
    </article>
</template>

<script>
import { createRouter, createWebHashHistory } from 'vue-router';
import PanelNav from '@/components/PanelNav.vue';

export default {
    name: 'Panel',
    components: {
        PanelNav,
    },
    props: {
       id: { type: String },
    },
    setup() {
        const router = createRouter({
            history: createWebHashHistory(),
            routes: [
                { path: '/content', name: 'content', component: ContentView },
                { path: '/', redirect: 'content' },
            ],
        });

        return {};
    },
}
</script>

EDIT 1

this hacky nonsense comes very close:

  • added :name="id" to the <router-view>
  • used addRoute with components object with the key as the id
<template>
    <article class="panel">
        <panel-nav></panel-nav>
        <router-view :name="id"></router-view>
    </article>
</template>

<script>
import { toRefs } from 'vue';
import { useRouter } from 'vue-router';
import PanelNav from '@/components/PanelNav.vue';
import ContentView from '@/views/ContentView.vue';

export default {
    name: 'Panel',
    components: {
        PanelNav,
    },
    props: {
       id: { type: String },
    },
    setup(props) {
        const { id } = toRefs(props);
        const router = useRouter();
        let comps = {};
        comps[id.value] = ContentView

        // add a route to the /home route
        router.addRoute('home', {
            path: `${id.value}/content`,
            components: comps,
        });

        return {};
    },
}
</script>

This allows me to go to /home/0/content and this will add the ContentView component to the Panel. However, as has been suggested by @jeremy castelli, I can only do this for a single Panel at a time. So if I go to /home/0/content, Panel 0 will show content. If I go to /home/1/content, Panel 0 will stop showing content and Panel 1 will show it instead - not quite what I'm after

EDIT 2:

Ultimately I've given up on this idea for this project and going back to a reactive state string and a series of on-view components with v-if="state === 'this-state'" to manage them. If I find a better way, I may go back and refactor

EDIT 3 (final)

It's been recommended to put each sub component into an <iframe> so they each have an individual URL.

This would require the panels to be a separate app but that's fundamentally what this is doing

obie
  • 578
  • 7
  • 23
  • no shouldn't create another instance of the router, I don't even think it will work . What you want to do is already possible with dynamic routes https://next.router.vuejs.org/guide/essentials/dynamic-matching.html and nested routes https://router.vuejs.org/guide/essentials/nested-routes.html – jeremy castelli Jan 11 '22 at 12:36
  • the issue with using nested routes is that it assumes a single application - if i use `/myview/panel/content`, this doesn't describe which Panel i'm targeting (out of a random 1-5 in this case) - i suspect that vue-router might not be the correct tool for this particular job but i'm interested to see if there's a solution – obie Jan 11 '22 at 12:39
  • you can't do something like /myview/panel/:panel_Id/content for example ? – jeremy castelli Jan 11 '22 at 12:41
  • sorry I think I understand, there is multiple panels displayed at the same time – jeremy castelli Jan 11 '22 at 12:43
  • that's right. There is possibly something in [this section](https://next.router.vuejs.org/guide/advanced/dynamic-routing.html#adding-routes-inside-navigation-guards) of the documentation which talks about dynamically adding and removing routes. So maybe for every new panel, a new route is added using the `id` prop... – obie Jan 11 '22 at 12:45
  • 1
    the problem is that you have a tree structure . A different route for each panel. its hard to reflect that on a single url . – jeremy castelli Jan 11 '22 at 12:50
  • hmmm - i found [this](https://next.router.vuejs.org/guide/essentials/named-views.html#nested-named-views) for creating named, nested views so you can target multiple ``s with a single click - it's not quite what i'm after but maybe close... They also have some [example code](https://codesandbox.io/s/nested-named-views-vue-router-4-examples-re9yl?initialpath=/settings/emails&file=/src/router.js) – obie Jan 11 '22 at 12:57

0 Answers0