Andreas Hunter
Andreas Hunter

Reputation: 5014

Vue 3: Emit event from parent to child component

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

Answers (5)

Micheal J. Roberts
Micheal J. Roberts

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

Younes Keraressi
Younes Keraressi

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

Stan Mayan
Stan Mayan

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

ssten
ssten

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

HugoDos
HugoDos

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

Related Questions