165

I have two nested components, what is the proper way to access to the child methods from the parent ?

this.$children[0].myMethod() seems to do the trick but it is pretty ugly, isn't it, what can be better way:

<script>
import child from './my-child'

export default {
  components: {
   child
  },
  mounted () {
    this.$children[0].myMethod()
  }
}
</script>
Saurabh
  • 71,488
  • 40
  • 181
  • 244
al3x
  • 1,756
  • 2
  • 13
  • 10
  • First, ask yourself do you really need to. If all your page state is in a store, as it should be, there is no need for parent-child communication. – bbsimonbb Nov 30 '17 at 08:25
  • 15
    @bbsimonbb State is different from events. This is specifically about triggering child events from parent. You could also do whatever you'd be using Vuex for by passing a prop downstream but this requires that child component watches the prop/store for changes so that you effectively emulate RPC with data changes which is just plain wrong when all you want is to trigger an action in the component. – Bojan Markovic Jul 13 '18 at 13:52

7 Answers7

352

You can use ref.

import ChildForm from './components/ChildForm'

new Vue({
  el: '#app',
  data: {
    item: {}
  },
  template: `
  <div>
     <ChildForm :item="item" ref="form" />
     <button type="submit" @click.prevent="submit">Post</button>
  </div>
  `,
  methods: {
    submit() {
      this.$refs.form.submit()
    }
  },
  components: { ChildForm },
})

If you dislike tight coupling, you can use Event Bus as shown by @Yosvel Quintero. Below is another example of using event bus by passing in the bus as props.

import ChildForm from './components/ChildForm'

new Vue({
  el: '#app',
  data: {
    item: {},
    bus: new Vue(),
  },
  template: `
  <div>
     <ChildForm :item="item" :bus="bus" ref="form" />
     <button type="submit" @click.prevent="submit">Post</button>
  </div>
  `,
  methods: {
    submit() {
      this.bus.$emit('submit')
    }
  },
  components: { ChildForm },
})

Code of component.

<template>
 ...
</template>

<script>
export default {
  name: 'NowForm',
  props: ['item', 'bus'],
  methods: {
    submit() {
        ...
    }
  },
  mounted() {
    this.bus.$on('submit', this.submit)
  },  
}
</script>

https://code.luasoftware.com/tutorials/vuejs/parent-call-child-component-method/

tony19
  • 125,647
  • 18
  • 229
  • 307
Desmond Lua
  • 6,142
  • 4
  • 37
  • 50
  • 45
    This is the correct answer, that actually read the actual question. The selected answer actually answers the opposite question (how to trigger a method on the parent from the child component). – Bojan Markovic Jul 13 '18 at 13:42
  • 1
    The link for [Event Bus](https://vuejs.org/v2/guide/components.html#Non-Parent-Child-Communication) that this answer is linking, is redirecting to [State Management](https://vuejs.org/v2/guide/state-management.html), after reading @bbsimonbb [comment](https://stackoverflow.com/questions/40957008/how-to-access-to-a-child-method-from-the-parent-in-vue-js?answertab=active#comment89723120_40957171), its kinda make sense. – Eido95 Jan 09 '19 at 17:16
  • 3
    It's worth to mention if you use `this.$refs.`, you shouldn't load child component dynamically. – 1_bug Nov 18 '19 at 10:56
  • thank you sir! You saved me a lot of trouble. I was fixing a production issue and was looking for answers desperately! <3 – Osama Ibrahim Jun 05 '20 at 21:30
  • 1
    `this.$ref.ref` seems to return an array. So for me `this.$refs.ref[0].autofocus();` worked – Jozef Plata Nov 17 '20 at 00:13
  • Event Bus was removed in Vue 3 ([doc](https://v3-migration.vuejs.org/breaking-changes/events-api.html#_2-x-syntax)) – alexandre-rousseau Mar 24 '22 at 11:46
42

For Vue 2.7 and Vue 3.2.x

<!-- Parent -->
<script setup>
import { ref, onMounted } from 'vue'
import ChildComponent from './components/ChildComponent.vue'

const childComponentRef = ref()

onMounted(() => {
    childComponentRef.value.doSomething()
})
</script>

<template>
    <ChildComponent ref="childComponentRef" />
</template>

script setup

<!-- Child -->
<script setup>
const doSomething = () => {
    console.log('Im batman')
}

defineExpose({
    doSomething
})
</script>

setup function

<!-- Child -->
<script>
import { defineComponent } from 'vue'

export default defineComponent({
    setup(props, context) {
        const doSomething = () => {
            console.log('Im batman')
        }

        context.expose({ doSomething })
    }
})
</script>

FYI: You should avoid doing this and use composables. Unless you have no control of a component (third-party plugins, etc.).

wobsoriano
  • 12,348
  • 24
  • 92
  • 162
  • 3
    You should use `InstanceType` when creating a `ref`. So `const childComponentRef = ref>()` – sanscheese Jul 14 '21 at 15:30
  • 3
    @sanscheese why? no typescript used here or in the question. Anyway defineExpose() is the thing i missed. Instantly worked after i put it in my child component. – erkage Dec 12 '21 at 16:29
  • yes, you have to set the ref to the target dom. the ref.value represent the dom, and then retrived the child function by call ref.value.method() – BlueboosuphuSKI Jan 05 '22 at 06:44
  • Neat solution. what about if we have multi level child components . (grand-child components) – dipenparmar12 Dec 08 '22 at 12:36
  • 2
    @dipenparmar12 That sounds like an expose-hell. Just use a composable. – wobsoriano Jan 05 '23 at 00:00
  • @wobsoriano using compsable will only work if you have global exports out of the composable. If you multiple children you can't easily use composable to distinguish between them. – lfmunoz Jun 20 '23 at 17:39
25

The suggested solution is for Vue 2, but if you end up here looking for a Vue 3 Composition API solution, you can do the following when migrating :

A child component in a template, which has method "doSomething" :

 <div class="form">                                                                                                                                                        
      <child-component ref="childComponentRef" />                                                                      
</div>  

With Vue 2 :

this.$refs.childComponentRef.doSomething( );
       

With Vue 3 Composition Api :

    setup( )
    {
        const childComponentRef = ref( );

        childComponentRef.value.doSomething( )

        return {
           childComponentRef
        }
     }  
RedHotPawn.com
  • 796
  • 9
  • 10
  • you should call `doSomething` inside `onMounted` otherwise `childComponentRef.value` could be `undefined`. – alexandre-rousseau Mar 24 '22 at 11:32
  • The Vue2 solution worked great! In my case, previously I was updating a `:key` with unique+timestamp to force logic in the child (re-render) but I only needed one specific method to occur and it was bad having all the mount logic re-run. – Kalnode May 19 '22 at 15:24
  • how about Vue 2 Composition API? I get 'Cannot find name 'defineExpose' – v3nt May 18 '23 at 11:45
24

Parent-Child communication in VueJS

Given a root Vue instance is accessible by all descendants via this.$root, a parent component can access child components via the this.$children array, and a child component can access it's parent via this.$parent, your first instinct might be to access these components directly.

The VueJS documentation warns against this specifically for two very good reasons:

  • It tightly couples the parent to the child (and vice versa)
  • You can't rely on the parent's state, given that it can be modified by a child component.

The solution is to use Vue's custom event interface

The event interface implemented by Vue allows you to communicate up and down the component tree. Leveraging the custom event interface gives you access to four methods:

  1. $on() - allows you to declare a listener on your Vue instance with which to listen to events
  2. $emit() - allows you to trigger events on the same instance (self)

Example using $on() and $emit():

const events = new Vue({}),
    parentComponent = new Vue({
      el: '#parent',
      ready() {
        events.$on('eventGreet', () => {
          this.parentMsg = `I heard the greeting event from Child component ${++this.counter} times..`;
        });
      },
      data: {
        parentMsg: 'I am listening for an event..',
        counter: 0
      }
    }),
    childComponent = new Vue({
      el: '#child',
      methods: {
      greet: function () {
        events.$emit('eventGreet');
        this.childMsg = `I am firing greeting event ${++this.counter} times..`;
      }
    },
    data: {
      childMsg: 'I am getting ready to fire an event.',
      counter: 0
    }
  });
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.28/vue.min.js"></script>

<div id="parent">
  <h2>Parent Component</h2>
  <p>{{parentMsg}}</p>
</div>

<div id="child">
  <h2>Child Component</h2>
  <p>{{childMsg}}</p>
  <button v-on:click="greet">Greet</button>
</div>

Answer taken from the original post: Communicating between components in VueJS

Yosvel Quintero
  • 18,669
  • 5
  • 37
  • 46
  • 1
    Thank you, so I will give a try to mutualize my code via events ! – al3x Dec 04 '16 at 10:06
  • 6
    when copy/pasting stuff it's nice to also mention the source. – Mihai Vilcu Dec 04 '16 at 17:04
  • 1
    @MihaiVilcu Improved the code example and added link to the post [Communicating between components in VueJS](https://dyrynda.com.au/blog/communicating-between-components-in-vuejs) – Yosvel Quintero Dec 04 '16 at 21:28
  • By the way : `$dispatch` and `$broadcast` have been removed in favor of more explicitly cross-component communication and more maintainable state management solutions, such as Vuex. https://vuejs.org/v2/guide/migration.html#dispatch-and-broadcast-replaced – al3x Dec 05 '16 at 07:59
  • 20
    This is helpful in communication from child to parent. But is there similar way to do it from parent to child? E.g. before I allow the user to add new child, I want all existing ones to be validated - the validation logic is in child, so I want to go through all of them and execute e.g. validate() method. – Mateusz Bartkowiak Sep 17 '17 at 18:48
  • 78
    This answers the opposite question to what was actually asked. Desmond Lua's answer answers the actual question. – Bojan Markovic Jul 13 '18 at 13:42
  • 4
    [Event bus is #1 on Chris Fritz' list of vue antipatterns](http://www.fullstackradio.com/87). Anything that can be modelled with events and distributed state can be modelled with global state and two way binding, and in general you'll be much better off. – bbsimonbb Jul 17 '18 at 07:43
  • 1
    Events cannot be modeled by state per-se, they are only modeled by state change. When you model events with state change you need to a) define a state object whose sole purpose is to model such events and b) observe and react to that change. In case of ephemeral events that is an anti-pattern -- best case: you're holding someone else's state you don't need. Worst case: you've re-inversed IoC. The assumption behind Elm-style architecture was (correct one, btw) that **majority** of events in UI have the sole or primary purpose to change the application state. But that is not true for all events. – Bojan Markovic Jul 17 '18 at 10:33
  • Useful tips about parent/child communication. You have successfully demonstrated how a parent can listen for when a child's event fires, and how a child can emit to a parent. But you failed to demonstrate how a parent can trigger a child's event to fire. So the question remains. How to access to a child method from the parent in vue.js – Air Jan 08 '21 at 15:38
5

Ref and event bus both has issues when your control render is affected by v-if. So, I decided to go with a simpler method.

The idea is using an array as a queue to send methods that needs to be called to the child component. Once the component got mounted, it will process this queue. It watches the queue to execute new methods.

(Borrowing some code from Desmond Lua's answer)

Parent component code:

import ChildComponent from './components/ChildComponent'

new Vue({
  el: '#app',
  data: {
    item: {},
    childMethodsQueue: [],
  },
  template: `
  <div>
     <ChildComponent :item="item" :methods-queue="childMethodsQueue" />
     <button type="submit" @click.prevent="submit">Post</button>
  </div>
  `,
  methods: {
    submit() {
      this.childMethodsQueue.push({name: ChildComponent.methods.save.name, params: {}})
    }
  },
  components: { ChildComponent },
})

This is code for ChildComponent

<template>
 ...
</template>

<script>
export default {
  name: 'ChildComponent',
  props: {
    methodsQueue: { type: Array },
  },
  watch: {
    methodsQueue: function () {
      this.processMethodsQueue()
    },
  },
  mounted() {
    this.processMethodsQueue()
  },
  methods: {
    save() {
        console.log("Child saved...")
    },
    processMethodsQueue() {
      if (!this.methodsQueue) return
      let len = this.methodsQueue.length
      for (let i = 0; i < len; i++) {
        let method = this.methodsQueue.shift()
        this[method.name](method.params)
      }
    },
  },
}
</script>

And there is a lot of room for improvement like moving processMethodsQueue to a mixin...

mohghaderi
  • 2,520
  • 1
  • 19
  • 12
  • Should the childMethodsQueue array be reset to empty after running the methods? – McGrew Oct 30 '21 at 23:22
  • I figured out that it should be reset to an empty array. There are several other changes needed, but it's too long to type here, so I posted an answer with all the changes needed to make it work. Thank you for giving me a starting point. – McGrew Oct 31 '21 at 05:04
  • 1
    @McGrew Thanks for posting your answer. I think issues are related to changes in Vue 3. This code still working in our old code base. Anyways, I believe these features needed to be in the framework, and I am not happy that I used it this way. These are points of improvements for Vue.js. – mohghaderi Nov 01 '21 at 17:00
1

I like mohghaderi's answer, but I ran into several issues with it, so I will use his sample code to show the changes I needed to make in order for it work. (In my own project, I'm using Vue 3 and the Options API.)

mohghaderi's Parent Component code with notes about my changes:

import ChildComponent from './components/ChildComponent'

new Vue({
  el: '#app',
  data: {
    item: {},
    childMethodsQueue: [],
  },
  // Note: In the template below, I added @child-methods-finished="childMethodsFinished" 
  //       as an event listener, so that we can reset the childMethodsQueue array to
  //       empty once the methods are finished.
  //       If you don't reset it, then the methods stay in there and cause problems.
  template: `
  <div>
     <ChildComponent :item="item" 
                     :methods-queue="childMethodsQueue"
                     @child-methods-finished="childMethodsFinished" />
     <button type="submit" @click.prevent="submit">Post</button>
  </div>
  `,
  methods: {
    submit() {
      this.childMethodsQueue.push({
        name: ChildComponent.methods.save.name,
        params: {}  // Note: delete the {} and put the name of your params, if you use a method that passes in params.
      })
    }
  },
  components: { ChildComponent },
})

mohghaderi's Child Component code with notes about my changes:

import { objectToString } from "@vue/shared"

export default {
    name: 'ChildComponent',
    props: {
      methodsQueue: { type: Array },
    },
    // Note:  I had to rewrite the watch option because it would not trigger.
    //        You have to add "deep, true" for arrays and objects.
    //        The function has to be called "handler" for it to work as well.
    watch: {
      methodsQueue: {
        handler() {
          this.processMethodsQueue()
        },
        deep: true,
      }
    },
    // Note:  Remove "mounted()" function if you don't want it to run on the mounted event.
    mounted() {
      this.processMethodsQueue()
    },
    methods: {
      save() {
          console.log("Child saved...")
      }, 
      processMethodsQueue() {
        if (!this.methodsQueue) return
        let len = this.methodsQueue.length

        if (!len) return  // Note:  This is required to prevent an infinite loop.
                          //        When we reset the childMethodsQueue array to empty,
                          //        it will trigger this method through the watch option,
                          //        so we need this in order to stop the cycle once we are done.

        // Note:  Instead of using ".shift()" to access an item in the array
        //        we need to use "[i]" otherwise we will get muliple calls of the method
        for (let i = 0; i < len; i++) {
          let method = this.methodsQueue[i]
          this[method.name](method.params)
        }

        // Note:  Now that we are done calling methods, we need to emit an event back to the parent
        //        so it can call it's method to reset the childMethodsQueue array to empty
        this.$emit('child-methods-finished')
      },
    },
  }
McGrew
  • 812
  • 1
  • 9
  • 16
-4

To communicate a child component with another child component I've made a method in parent which calls a method in a child with:

this.$refs.childRef.childMethod()

childRef is the reference of your child component and childMethod can just be replaced with whatever method you have in your child component.

And from the other child I've called the root method:

this.$root.theParentMethod() // It works with Bootstrap Vue
this.$parent.theParentMethod()

It worked for me.

Jonathan Arias
  • 471
  • 7
  • 18