January 21, 2022
Communication, either it is between two humans or between two components, has always been a key factor in establishing strong relationships and making things successful. We leave the human part for some other day, and today we’ll focus only on communication between components.
For most of the common cases, props and events are sufficient to communicate but there are few other options listed below.
1. Using Props
2. Using Events
3. Using this.$refs
4. Using Event Bus
5. Using v-model
6. Using provide/Inject
Let’s explore them one by one.
Props allow you to pass any kind of data from parent to child component . It also provides the flexibility of defining the data types we want to receive in the child component. All props form a one-way-down binding between the child property and the parent one.
Prop changes are reactive which means every time the parent component is updated, the props values in the child components will be updated.
<child-component title="I am the tiltle">
Let’s assume we have two components one child component which accepts a title prop and displays it in template.
//ChildComponent
<template>
<div>{{title}}</div>
</template>
<script>
export default{
props: {
title: String
}
data(){
return {
}
}
}
</script>
and one parent component which uses the child component and passes the message to child component.
//ParentComponent
<template>
<child-componet :title=“title” />
</template>
<script>
import ChildComponet from ‘ChildComponent’;
export default {
components: {ChildComponent},
data(){
return {
title: ‘Hello, I’m from parent component.’
}
}
}
</script>
If you want to pass data from child to parent component, how do you do this? This can be achieved by events. Events allow us to inform the parent component if something happens in child component.
Again, let’s assume we have two components. In the parent component we bind the custom event to some method and listen to event.
//ParentComponent.vue
<template>
<child-componet @custom-event=“handleCustomEvent” />
</template>
<script>
import ChildComponet from ‘ChildComponent’;
export default{
props: [‘message’],
component: [ChildComponent],
data(){
return {
message: ‘Hello, I’m from parent component.’
},
methods: {
handleCustomEvent(data){
console.log(data) //[1,2,3,4]}
}
}
}
</script>
In the child component, we emit the event using this.$emit(‘event-name’, data) syntax.
//ChildComponent.vue
<template>
<button @click=“handleClick” > Click Me, Please </button>
</template>
<script>
export default {
props: [‘message’],
data(){
return {
customData: [1,2,3,4]
},
methods: {
handleClick(){
this.$emit("custom-event", this.customData)
}
}
}
}
</script>
There come moments when we are in the parent component and want to access child component’s properties or call a method , e.g. calling some api in child component when something happens in parent component. We can achieve this with $refs.
<child-component ref="refName">
In the parent component, we use ref on the child component and by this.$refs[‘refName’] syntax we can access the child component.
//ParentComponent.vue
<template>
<child-componet ref=“childComponent” />
</template>
<script>
import ChildComponet from ‘ChildComponent’;
export default {
component: {ChildComponent},
data(){
return {
message: ‘Hello, I’m from parent component.’
},
},
methods: {
handleCustomEvent(){
this.$refs.childComponent.callSomeApi()// calling child method
this.$refs.childComponent.message = "changed this property from parent" //changing child component's data property
}
}
}
</script>
and here is the child component.
//ChildComponent
<template>
</template>
<script>
export default {
data(){
return {
message: "Hello, I’m the child."
},
},
methods: {
callSomeApi(){
//
}
}
}
</script>
This is super helpful when components don’t have parent child relationship, but we still want them to communicate with each other. For this to work, we need to listen for an event in a component and some other component to fire that event.
Bus.$emit("event-name", data); // emit the event
Bus.$on("event-name",callback); // listen the event
Bus.$off("event-name", callback); // destroy the event
First of all, we need to create an event bus in our main.js file
//creating event bus
import Vue from 'vue'
import App from './App.vue'
export const eventBus = new Vue();// creating an event bus.
new Vue({
render:h=>h(App),
}).$mount('#app')
and then let’s assume we have two unrelated components…
//SomeComponent
<template>
<button @click=“handleClick” />
</template>
<script>
import { eventBus } from "./main.js"
export default{
props: [‘message’],
component: {ChildComponent},
data(){
return {
message: "Hello, I’m from parent component."
},
methods: {
handleClick(data){
eventBus.$emit("message-sent", this.message)
}
}
}
</script>
//SomeOtherComponent
<template>
</template>
<script>
import { eventBus } from "./main.js"
export default{
props: [‘message’],
component: {ChildComponent},
data(){
return {}
},
methods: {
handleMessage(data){
console.log(data)
}
},
mounted(){
evnetBus.$on("message-sent", (data) => this.handleMessage(data))
}
}
</script>
We can combine both props and events and use v-model for data sharing. It is often used for input based component. By default, v-model on a component uses value as the prop and input as the event but can be customized.
//ParentComponent
<template>
<div>
<child-component v-model="value" />
</div>
</template>
<script>
import ChildComponent from "ChildComponent.vue"
export default {
components: {ChildComponent},
data(){
return {
value: ''
}
}
}
</script>
and in the child component
//ChildComponent
<template>
<div>
<input type="text" :value="value" @input=“handleInput” />
</div>
</template>
<script>
export default {
props: {
value: String
},
methods: {
handleInput(event) {
this.$emit('input', event.target.value);
}
}
}
</script>
Personally I never came across a situation where I’ve to use this technique to communicate between components. This is used to allow an parent component to serve as a dependency injector for all its descendants/child components , regardless of how deep the component hierarchy is, as long as they have parent/child relationship. The provide option should be an object or a function that returns an object.
//ParentComponent
<template>
<child-component />
</template>
<script>
import ChildComponent from 'ChildComponent.vue'
export default {
provide: {
foo: ‘bar’
},
components:{ChildComponent}
}
</script>
in any child/descendant component
//any descendant component
<template>
<div>{{foo}}
</template>
export default {
inject: [foo],
}
🙌 🙌 This is all from my end, hope you enjoyed reading it.
📝 Thoughts by Zahid Jabbar.
A developer.