7

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.

peterjwolski
  • 153
  • 2
  • 10
  • What exactly are you trying to achieve? Sure, scoped slots can be used to share _some_ of the `Child` component data to the slot __template__. But functions? Don't think so. Also just because your `modal` component is used inside scoped slot template does not make all `Child` data accessible to it. You still need pass it down into `modal` by props.... – Michal Levý Nov 27 '19 at 17:54
  • 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? – peterjwolski Nov 27 '19 at 18:07

1 Answers1

7

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)

tony19
  • 125,647
  • 18
  • 229
  • 307
Michal Levý
  • 33,064
  • 4
  • 68
  • 86
  • I don't understand how this has the flexibility of a slot? Say, instead of a modal appearing on click, I wanted the row height to expand, and another table to appear beneath the clicked on row. How could this be acheived? I thought the way would be to parse the component I wanted to appear as a slot into my table component? – peterjwolski Nov 27 '19 at 23:56
  • Read Scoped Slot in the doc for being update on solving this kind of problems: https://vuejs.org/v2/guide/components-slots.html#Scoped-Slots – SeyyedKhandon Jul 12 '20 at 07:11