Search code examples
vue.jsvuejs2vuejs-slots

How to access child component's reactive data in a slot?


Currently, I have a parent component, which renders a child component, and also passes a component (a modal, called Modal.vue) into the child as a slot.

Parent.vue

<ChildComponent :id='1>
 <modal slot="modal" :postTitle="'test'" />
</ChildComponent>

Child.vue

export default {
props : ['id'],
data(){
return {
 modalHeading : "Heading"
}
}
<template>
<slot name="modal"></slot>
</template>

Modal.vue

<script>
export default {
  name: "model",
  props: ["postTitle"],
  data() {
    return {
      heading: "test"
    };
  },
  mounted() {
    console.log("mounted modal slot");
    console.log(this.postTitle);
    console.log(this.modalHeading);
};
</script>

<template>
  <div>
    <h2>{{ modalHeading }}</h2>
    <h3>This will be the modal slot</h3>
    <p>{{ postTitle }}</p>
  </div>
</template>

My question is, how can I access the child's data (and functions) in my slot component?

I've been trying to get it to work with scoped slots, but can't seem to crack the syntax.


Solution

  • I'm trying to basically display some of the child data in the modal slot. Essentially The parent component is a page, the child component is a table, the slot is a modal. For each table row clicked on, a modal appears with the tables data in it. How would you suggest doing this

    Maybe something like this ?

    <template>
      <div>
        <table>
          <row v-for="item in items" @click="onRowClick(item)"
             ....show some data
          </row>
        </table>
        <modal v-if="activeItem" :item="activeItem" @onClose="onModalClose" />
      </div>
    </template>
    <script>
    export default {
      name: 'child',
      props: ["items"]
      data() {
        return {
          activeItem: null
        }
      },
      methods: {
        onRowClick(item) {
          this.activeItem = item;
        },
        onModalClose() {
          this.activeItem = null;
        }
      }
    }
    </script>
    

    I don't understand how this has the flexibility of a slot?

    Well it's not as flexible as a slot but it gets the job done :)

    If you really need to design your Child/table component in a way that allows to use it in different places and change how part of it looks (row detail in your case), slots are way to go....

    Slots

    Slots is distribution outlets for content. When you place a slot inside your component, you are saying "My parent component can provide a partial template. When this template is rendered (inside parent's component scope - important because that template has access only to parent's data/methods etc.), I'll take that rendered result and put it at this exact place inside my own template". Using regular slot is like passing HTML into your component.

    My question is, how can I access the child's data (and functions) in my slot component?

    You must give a slot template access to some of child's data explicitly. Let's say your child component has prop item. You can define scoped slot inside it like this:

    <slot name="rowExpander" :itemProp="item"></slot>
    

    ...and use it inside parent like this:

    <template v-slot:rowExpander="slotProps">
      <MyRowExpanderComponent :item="slotProps.itemProp" />
    </template>
    

    Or using ES2015 destructuring

    <template v-slot:rowExpander="{ itemProp }">
      <MyRowExpanderComponent :item="itemProp" />
    </template>
    

    It's possible to pass almost anything into the scoped slot - data/props/method/computed, you can even use instance properties like $data or $props:

    <slot name="rowExpander" :childProps="$props"></slot>
    

    It's not possible to pass child instance itself:

    <slot name="rowExpander" :child="this"></slot>
    

    (mainly because this is not defined inside the template - I think it can be done if you write your component without template using render function but that's another story)

    It's important to understand that scoped slot template is just regular Vue template. It has access to all instance properties of the component where it is defined (parent) and additionally to a slotProps object. When you use component inside scoped slot template, that component doesn't have access to all child data or slotProps auto-magically - you still need to pass it to it explicitly (like in my MyRowExpanderComponent example above)