I have a component (Child) that has a slot. It also has a button that I want to trigger a function in the component in the slot (GrandChild). Is that possible? And how to achieve that? I have tried using ref on the slot, but without success. Also looked on using a scoped slot. I'm using vue3 with Typescript.
The Child does not know what component that should be in the slot, but maybe that could be sent as a prop.
<!-- Parent.vue -->
<script setup lang="ts">
import Child from './TestSlotsChild.vue';
import GrandChild from './TestSlotsGrandChild.vue';
</script>
<template>
<div class="parent">
<h2>I'm the parent</h2>
<Child>
<GrandChild/>
</Child>
</div>
</template>
<!-- Child.vue -->
<script setup lang="ts">
</script>
<template>
<div class="child">
<h3>I'm the child</h3>
<slot >
</slot>
<button>Call myMethod in GrandChild</button>
</div>
</template>
<!-- GrandChild.vue -->
<script setup lang="ts">
import { ref } from 'vue';
const called = ref(false);
function myMethod() {
called.value = true;
console.log('myMethod called in grandchild component');
}
</script>
<template>
<div class="grandchild">
<h4>I'm the grandchild</h4>
<p v-if="called">Parent called <em>myMethod</em> of grandchild.</p>
</div>
</template>
Edit: An additional issue I ran into was when I wanted to make a for loop in the parent. Then I got the following error on myMethod: "Property 'myMethod' does not exist on type '(CreateComponentPublicInstanceWithMixins<ToResolvedProps<{}, {}>, { myMethod: () => void; }, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, ... 18 more ..., {}> | null)[]'.ts-plugin(2339)"
Is there a solution for that?
I guess I must have unique refs for each element in the array, but couldn't really get it to work.
<!-- Parent.vue -->
<script setup lang="ts">
import { useTemplateRef, computed } from 'vue';
import Child from './TestSlotsChild.vue';
import GrandChild from './TestSlotsGrandChild.vue';
const gc = useTemplateRef('grandchild');
const someArray = computed(() => [
{childComponent: Child, grandChildComponent: GrandChild},
{childComponent: Child, grandChildComponent: GrandChild},
]);
</script>
<template>
<div class="parent">
<h2>I'm the parent</h2>
<div v-for="(element, index) in someArray" :key="index">
<component
:is=element.childComponent
@click:btn="gc?.myMethod()"
>
<!-- <component :is="dialog.component" ref="dialogContets2"/> -->
<component :is="element.grandChildComponent" ref="grandchild"/>
</component>
</div >
</div>
</template>
Expose the method from the GrandChild component in order to get access to it using a ref :
<script setup lang="ts">
import { ref } from 'vue';
const called = ref(false);
function myMethod() {
called.value = true;
console.log('myMethod called in grandchild component');
}
defineExpose({
myMethod //<--- expose it this way
})
</script>
<template>
<div class="grandchild">
<h4>I'm the grandchild</h4>
<p v-if="called">Parent called <em>myMethod</em> of grandchild.</p>
</div>
</template>
Then you can emit an event from the Child component :
<script setup lang="ts">
const emit = defineEmits(['click:btn'])
</script>
<template>
<div class="child">
<h3>I'm the child</h3>
<slot>
</slot>
<button @click="emit('click:btn')">Call myMethod in GrandChild</button>
</div>
</template>
Finally add a ref on the GrandChild component and call its exposed method using the emitted event from the Child component :
<script setup>
import { useTemplateRef } from 'vue';
import Child from './Child.vue';
import GrandChild from './GrandChild.vue';
const gc = useTemplateRef('grandchild')
</script>
<template>
<div class="parent">
<h2>I'm the parent</h2>
<Child @click:btn="gc.myMethod()">
<GrandChild ref="grandchild" />
</Child>
</div>
</template>