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>
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/
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:
$on() - allows you to declare a listener on your Vue instance with which to listen to events $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 } });
{{parentMsg}}
{{childMsg}}
Answer taken from the original post: Communicating between components in VueJS
If you end up here looking for Vue 3 script setup
<!-- Parent -->
<template>
<ChildComponent ref="childComponentRef" />
</template>
<script setup>
import { ref, onMounted } from 'vue'
import ChildComponent from './components/ChildComponent.vue'
const childComponentRef = ref()
onMounted(() => {
childComponentRef.value.doSomething()
})
</script>
<!-- Child -->
<script setup>
const doSomething = () => {
console.log('Im batman')
}
// Only available in Vue >= 3.1.3
// No need to import
defineExpose({
doSomething
})
</script>
If your Vue version is < 3.1.3
, you will have to use setup
function and return the doSomething
function to access it in the parent component.
<!-- Child -->
<script>
import { defineComponent } from 'vue'
export default defineComponent({
setup() {
const doSomething = () => {
console.log('Im batman')
}
return { doSomething }
}
})
</script>
InstanceType
when creating a ref
. So const childComponentRef = ref<InstanceType<typeof ChildComponent>>()
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
}
}
doSomething
inside onMounted
otherwise childComponentRef.value
could be undefined
.
: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.
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...
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')
},
},
}
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.childMethod()
And from the another child I've called the root method:
this.$root.theRootMethod()
It worked for me.
Success story sharing
this.$refs.
, you shouldn't load child component dynamically.this.$ref.ref
seems to return an array. So for methis.$refs.ref[0].autofocus();
worked