Reputation: 5014
Now I need to emit event from parent to child component. I see in vue version 3 $on
, $off
and $once
instance methods are removed. Application instances no longer implement the event emitter interface.
How now I can emit events from parent component and listen from child component in vue version 3?
Upvotes: 19
Views: 35588
Reputation: 4160
You can do this by using scoped slots.
For example, in the parent to the "slotted" component:
Parent.vue
:
<script setup lang="ts">
import { useToggle } from '@/composables'
const props = defineProps({
show: {
type: Boolean,
required: true
}
})
const { isOpen, close, open, toggle } = useToggle(props.show)
</script>
<template>
<slot
:on-open="open"
:on-close="close"
:on-toggle="toggle"
/>
</template>
*Where we have used a basic toggle composable.
In the Grandparent.vue
component, we can then scope some of the method trigger events to the Child.vue
component of the parent as follows:
GrandParent.vue
:
<template>
<Parent>
<template #default="{ onToggle }">
<Child
@toggle="onToggle"
/>
</template>
</Parent>
</template>
*Change #default
to whatever your slot name is called, or leave as default if no name.
And then in the Child.vue component, we make sure to emit the event up:
Child.vue
:
<script setup lang="ts">
const emit = defineEmits<{
(e: 'toggle'): void
}>()
const toggle = () => {
emit('toggle')
}
</script>
<template>
<button @click="toggle">Toggle Event</button>
</template>
Upvotes: 2
Reputation: 21
If you were like me calling some event on this.$root.$on(...)
and this.$root.$emit(...)
in Vue2 from any parent/child to any child/parent for somehow keep your code cleaner rather then using bunch of emits and props respectively and have your code blows up..
from Vue3 doc, event bus pattern can be replaced by using an external library implementing the event emitter interface. use a library that implement a pub-sub pattern or write it. vue3 event description
Now if you're using the Option-API (like vue 2) y need to import that event file then using it right a way in any component.
if you are using the <script setup>
you need add extra step in order to you that event library heres the code.
here's a basic example of pub-sub javascript pattern, don't forget to add the off method and call it on beforeUnmounted
(v3), beforeDestroy
(v2) to not have multiple function execution for each mounted call )
//event.js
class Event{
constructor(){
this.events = {};
}
on(eventName, fn) {
this.events[eventName] = this.events[eventName] || [];
this.events[eventName].push(fn);
}
emit = (eventName, data)=> (this.events[eventName]) ? this.events[eventName].forEach( fn => fn(data)) : '' ;
}
export default new Event();
if you're doing vue2 like syntax Option-API: //in vue component
import event from './event';
//mounted or any methods
even.on('GG', data=> console.log(`GG Event received ${data}`))
//obviously you have to emit that from another component //...
import event from './event';
//on mounted or methods click or...
even.emit('GG', {msg:"Vue3 is super Cool"});
if you're using the <script setup>
which means all variables and methods are exposed by default to the template.
//in main.js
import event from './event.js';
//..
app.config.globalProperties.$event = event;
//..
//note if you have an error make sure that you split the the app chaining, like this :
let app = createApp(App);
app.config.globalProperties.$event = event;
app.mount('#app');
//add a file called useEvent.js
// useEvent.js
import { getCurrentInstance } from 'vue'
export default useEvent => getCurrentInstance().appContext.app.config.globalProperties.$event;
//using it in a <script setup>
import useEvent from '@/useEvent'
const event = useEvent();
event.emit('GG');
Upvotes: 1
Reputation: 458
You can access to child methods by using Refs
https://v3.vuejs.org/guide/composition-api-template-refs.html
<!-- Parent -->
<template>
<ChildComponent ref="childComponentRef" />
</template>
<script>
import { ref } from 'vue'
import ChildComponent from './components/ChildComponent.vue'
export default {
setup( ) {
const childComponentRef = ref()
childComponentRef.value.expandAll();
return { childComponentRef }
}
}
</script>
<!-- Child -->
<script>
export default {
setup() {
const expandAll= () => {
//logic...
}
return { expandAll}
}
}
</script>
Upvotes: 21
Reputation: 2049
I've came up with a way of emitting an event from a parent to a child component. Be aware as this is a very ugly workaround !!
//child
setup(){
// get the current instance
const instance = getCurrentInstance();
// function to find the parent instance
const getParentComponent = (name: string) => {
let component = null;
if (instance) {
let parent = instance.parent;
while (parent && !component) {
if (parent.type.name === name) {
component = parent;
}
parent = parent.parent;
}
return component;
} else {
return null;
}
};
// listener that will be called from within the parent component
const eventFunction = () => {
console.log('event fired !!')
}
onMounted(() => {
const $parent = getParentComponent('ParentComponentName');
// register the current instance to the parent component
if($parent && $parent.props && $parent.props.childInstances){
($parent.props.childInstances as any[]).push(instance)
}
})
return {
eventFunction
}
}
//parent
name: 'ParentComponentName',
props: {
childInstances: {
type: Array as PropType<any[]>,
required: false,
default: () => [] as any[]
}
},
setup(props){
const emitChildComponentEvent = (event: string) => {
if(props.childInstances.length > 0){
props.childInstances.forEach((component: any) => {
if(typeof component.proxy[event] === 'function'){
component.proxy[event]()
}
})
}
}
onMounted(() => {
emitChildComponentEvent('eventFunction');
})
}
Upvotes: 1
Reputation: 329
You would not listen from the child component for parent events, you would instead pass a prop down to the child and if the child component needs to update the data you would emit an event from child to parent to update the state.
Read only life cycle: Parent > Prop > Child
Read/Update life cycle: Parent > Prop > Child > Emit > Parent > Update > Child Updates
Upvotes: 6