Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Accessing arbitrary slots in Vue

Say I want to create a multi layered Component for reuse like a 'Tab' Ui.

So the using developer could write:

<tabs>
  <tab label="My First Tab">
    Content for first tab which could contain components, html, etc.
  </tab>
  <tab label="My Second Tab">
    More Content
  </tab>
</tabs>

Basically, they would use the Tabs and Tab components in such a way that I have no idea how many Tab components the dev will use in the Tabs. How do I access the Tab components to, for example, show and hide them per tab UI functionality?

I've tried using this.$children and this.slots.default but was not actually able to access the Tab data to show and hide it. To be fair, I'm writing in Typescript and the issue may be more difficult because of it.

Something like:

<template>
    <div class="tabs">
        <slot /> <!-- Location of the Tab's -->
    </div>
</template>

<script lang="ts">
import { Component, Vue } from "vue-property-decorator";

@Component({
    ...
})
export default class Tabs extends Vue {
    public tabs: any[] = [];
        
    public created() {
        this.tabs = this.$slots.default;
        // this.tabs is a set of VNode's, 
        //    I can't figure out how to access the components
        //    I see a prop 'componentInstance' but when I try to
        //    access it, it says undefined
        // this.select(0);
        let tab = this.tabs.find((obj: any, index: number) => {
            return index === 0;
        }); // tab is undefined :(
    }

    public select(index: number) {
        (this.$slots.default[index] as any).show = true;
        // Accessing Vnode instead of Component :(
        // this.$children[0]["show"] = true; // Read only :(
        // this.tabs[index].show = true; // blerrggg :(

        // Vue.nextTick();
    }
}
</script>

I've looked at some GitHub libs for Tabs in Vue and the logic seems very complicated. I'm assuming from the existence of slots/children there is a more straight-forward approach to accessing child components. Maybe that's overly hopeful on my part.

Anyone know how to access, pass or change data in a child slot if you don't know how many children will be there (ie you didn't explicitly write them in the parent Component)?

like image 379
dmcblue Avatar asked Nov 23 '25 12:11

dmcblue


1 Answers

If the issue is that you have to wait until the tab components are mounted, the mounted lifecycle hook exists for this sort of thing. There is a potential problem with using the $children property though, as outlined in the Vue docs:

Note there’s no order guarantee for $children, and it is not reactive. If you find yourself trying to use $children for data binding, consider using an Array and v-for to generate child components, and use the Array as the source of truth.

Since the order is not guaranteed, this.$children[index] is not guaranteed to give you the tab you expect.

If you were to go with the approach suggested, using v-for and an array, it might look something like this:

<template>
    <div class="tabs">
        <tab v-for="(tabName, index) in tabs" :key="tabName" :label="tabName" :ref="`tab-${index}`">
            <slot :name="tabName"></slot>
        </tab>
    </div>
</template>

<script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator";

@Component({
    ...
})
export default class Tabs extends Vue {
    @Prop(Array) tabs: string[];

    public mounted() {
        this.select(0);
    }

    public select(index: number) {
        this.$refs[`tab-${index}`].show = true;
    }
}
</script>

I put the slot inside a tab so that you know all of the child components are going to be wrapped in a tab component, meaning all tabs children are guaranteed to be tab components, and your $refs will all have the expected tab properties. You would use this like so:

<tabs :tabs="['My First Tab', 'My Second Tab']">
    <template slot="My First Tab">
        Content for first tab which could contain components, html, etc.
    </template>
    <template slot="My Second Tab">
        More content
    </template>
</tabs>
like image 187
Tomhah Avatar answered Nov 25 '25 08:11

Tomhah



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!