SebT
SebT

Reputation: 145

Vue 3 - Get access to router-view instance in order to call child methods

I’m trying to migrate a Vue 2.x app to Vue 3.x. Unfortunately, for the past two days, I’m struggling to find working solution to this simple issue :

My application is for mobile device and, at the top of the screen, I have a top bar with 2 contextual buttons on the left and on the right. These buttons trigger methods that are related to the view loaded in my <router-view/> and hosted in it.

My Vue 2 app it worked perfectly well following the advice of this post :

[Vue 2] App.vue

<template>
    <app-bar>
        <bar-btn @click="$refs.routerView[$route.meta.leftBtn.methodName]($route.meta.leftBtn.arguments)">Left btn</bar-btn>
        <div>View title</div>
        <bar-btn @click="$refs.routerView[$route.meta.rightBtn.methodName]($route.meta.rightBtn.arguments)">Right btn</bar-btn>
    </app-bar>
    <main>
        <router-view ref="routerView"/>
    </main>
</template>

Methods names and optional arguments where stored in my meta data of my routes :

[Vue 2] Router.js

{
    name: 'View 1',
    path: '/',
    component: MyView1,
    meta: {
        requiresAuth: false,
        leftBtn:  { methodName: 'showLeftDialog',  arguments: 'myArgument' }
        rightBtn: { methodName: 'showRightDialog', arguments: 'myArgument' }
    },
},

In Vue 2, I had access to the router-view instance by using: this.$refs.routerView

Unfortunately, it does not work anymore with Vue 3 !

After having spent a lot of time, I haven't found a proper way to get access to my child instance loaded in my <router-view/> in order to trigger my methods hosted in it.

[Vue 3] This does NOT work:

Does not work:
this.$refs.routerView[this.$route.meta.leftBtn.methodName](this.$route.meta.leftBtn.arguments)

Does not work:
this.$router.currentRoute.value.matched[0].components.default.methods[this.$route.meta.leftBtn.methodName](this.$route.meta.leftBtn.arguments)

Does not work:
this.$refs.routerView.$refs    => this is an empty object

Put it simply, how can I have access to the instance of child component loaded in router-view with Vue 3 ?

Any help on this would be very much appreciated.

Upvotes: 6

Views: 4705

Answers (2)

agm1984
agm1984

Reputation: 17132

This didn't work for me, so I did this:

// useEvent.js

class Event {
    constructor(){
        this.events = {};
    }

    on(eventName, fn) {
        this.events[eventName] = this.events[eventName] || [];
        this.events[eventName].push(fn);
    }

    emit(eventName, data) {
        if (!this.events[eventName]) {
            throw new Error(`Event not registered: '${eventName}'`);
        }

        this.events[eventName].forEach(fn => fn(data));
    }
}

export default new Event();

This solution is similar to the Vue2 global event bus.

We are going to create a class that can listen and emit from arbitrary components. I would consider this solution probably-illegal (anti-pattern territory in Vue 3) in most-every case except this one where we are trying to call a function in a child component while the child is inaccessible behind the router-view component.

// parent

<script setup>
import event from '@/composables/useEvent';

const someFunction = () => {
    event.emit('refetch-salmon', { yolo: true })
};
</script>

<template>
    <router-view :tab-height="tabHeight"></router-view>
</template>

// child

<script setup>
import { onMounted } from 'vue';
import event from '@/composables/useEvent';

onMounted(() => {
    event.on('refetch-salmon', data => fetchSalmon(data));
});
</script>

Note, make sure you try to use a unique event name so you can easily search your code base for all instances that are listening to it; be verbose.

Upvotes: 0

tony19
tony19

Reputation: 138276

Vue Router 4's <router-view> exposes the rendered view component in a v-slot prop that can be rendered with <component>, to which you could apply a template ref:

<router-view v-slot="{ Component }">
  <component ref="view" :is="Component" />
</router-view>

Then the component's methods can be accessed via $refs.view.$.ctx:

<bar-btn @click="$refs.view.$.ctx[$route.meta.leftBtn.methodName]($route.meta.leftBtn.arguments)">Left btn</bar-btn>
<bar-btn @click="$refs.view.$.ctx[$route.meta.rightBtn.methodName]($route.meta.rightBtn.arguments)">Right btn</bar-btn>

demo

Upvotes: 19

Related Questions