The data flow in Vue is unidirectional. What this means is data flows from parent to child components using props. In case, we want to pass information from the child to the parent component we make use of the $emit
method.
$emit
is a method in Vue used for passing information from child to the parent component. The way it works is $emit
method is used to call a custom event from the child component which gets handled by the parent component. You can pass data to this custom event from the child component.
Let’s us understand $emit
and custom events with an example. In our example, we will have a list of TV series to which the user can subscribe. By default, the subscribe value will be false.
We will start by creating an application using Vue CLI.
vue create child-parent-component
Once, the installation is complete, we can navigate to the folder and run the server to load the application.
❯ cd child-parent-component
❯ npm run serve
This will run our server on port 8080 http://localhost:8080/
Let’s edit the App.vue
file and remove any unwanted code.
// App.vue
<template>
<TvShow />
</template>
<script>
import TvShow from "./components/TvShow.vue";
export default {
name: "App",
components: {
TvShow,
},
};
</script>
Here, we we have imported TvShow
component but have not created it yet. Let’s go ahead and create one.
❯ touch src/components/TvShow.vue
We will open the file and add some code.
// TvShow.vue
<template>
<h2>Tv Show</h2>
</template>
<script>
export default {
name: "TvShow",
};
</script>
Next, we will declare some data in the parent component App.vue
which can be passed to the child component TvShow
.
data() {
return {
series: [
{
id: 1,
name: "Breaking Bad",
subscribe: false,
},
{
id: 2,
name: "Squid Game",
subscribe: false,
},
{
id: 3,
name: "The Last of us",
subscribe: false,
},
{
id: 4,
name: "Dark",
subscribe: false,
},
],
};
};
Using v-for
we will loop through the series
data and pass them using props to the child component TvShow
.
<template>
<div v-for="show in series" :key="show.id">
<TvShow :id="show.id" :name="show.name" :subscribe="show.subscribe" />
</div>
</template>
So the entire App.vue
file now contain the following code:
<template>
<div v-for="show in series" :key="show.id">
<TvShow :id="show.id" :name="show.name" :subscribe="show.subscribe" />
</div>
</template>
<script>
import TvShow from "./components/TvShow.vue";
export default {
name: "App",
components: {
TvShow,
},
data() {
return {
series: [
{
id: 1,
name: "Breaking Bad",
subscribe: false,
},
{
id: 2,
name: "Squid Game",
subscribe: false,
},
{
id: 3,
name: "The Last of us",
subscribe: false,
},
{
id: 4,
name: "Dark",
subscribe: false,
},
],
};
},
};
</script>
Let’s make changes in the TvShow.vue
file to display the data.
// TvShow.vue
<template>
<div>
<h2>{{ name }}</h2>
<button>{{ subscribe ? "Subcribed" : "Not Subscribed" }}</button>
</div>
</template>
<script>
export default {
name: "TvShow",
props: ["id", "name", "subscribe"],
};
</script>
Whenever, the button is clicked, user can subscribe or unsubscribe to the Tv series. To make this happen, we need to pass this information from the child component TvShow
to the parent component App
.
For this to work, we will first create a custom event, toggle-subscribe
on the button click.
<button @click="buttonClick">
{{ subscribe ? "Subcribed" : "Not Subscribed" }}
</button>
Next, we will declare the method which emits the custom event toggle-subscribe
to the parent component along with the id
of the object:
methods: {
buttonClick() {
this.$emit("toggle-subscribe", this.id);
},
}
Here, method $emit
will take the name of the custom event toggle-subscribe
and the id of the object as the argument which gets passed to the parent component App
.
In the App.vue
let’s pass the custom event to TvShow
component.
// App.vue
<template>
<div v-for="show in series" :key="show.id">
<TvShow
:id="show.id"
:name="show.name"
:subscribe="show.subscribe"
@toggle-subscribe="subscribeTvShow"
/>
</div>
</template>
Notice we are passing the custom event toggle-subscribe
using the v-on
shorthand @
.
@toggle-subscribe="subscribeTvShow"
So, the entire TvShow
file will contain the following code:
<template>
<div>
<h2>{{ name }}</h2>
<button @click="buttonClick">
{{ subscribe ? "Subcribed" : "Not Subscribed" }}
</button>
</div>
</template>
<script>
export default {
name: "TvShow",
props: ["id", "name", "subscribe"],
methods: {
buttonClick() {
this.$emit("toggle-subscribe", this.id);
},
},
};
</script>
subscribeTvShow
is a method in App.vue
where we will write the toggle logic.
// App.vue
methods: {
subscribeTvShow(showId) {
const show = this.series.find((show) => show.id == showId);
show.subscribe = !show.subscribe;
},
}
In this method, we are finding the object via the id
property which gets passed in the $emit
method from the child component TvShow
.
So the entire App.vue
now contains the following code:
<template>
<div v-for="show in series" :key="show.id">
<TvShow
:id="show.id"
:name="show.name"
:subscribe="show.subscribe"
@toggle-subscribe="subscribeTvShow"
/>
</div>
</template>
<script>
import TvShow from "./components/TvShow.vue";
export default {
name: "App",
components: {
TvShow,
},
data() {
return {
series: [
{
id: 1,
name: "Breaking Bad",
subscribe: false,
},
{
id: 2,
name: "Squid Game",
subscribe: false,
},
{
id: 3,
name: "The Last of us",
subscribe: false,
},
{
id: 4,
name: "Dark",
subscribe: false,
},
],
};
},
methods: {
subscribeTvShow(showId) {
const show = this.series.find((show) => show.id == showId);
show.subscribe = !show.subscribe;
},
},
};
</script>
With this code in place, we can now subscribe or unsubscribe a Tv series from the list using the button.
The above example demonstrates how we can use the $emit
and custom events to pass information from the child to the parent component.
Declaring and validating custom events
Similar to props we can also declare and validate the custom events used by the components.
To declare the custom events, we can make of the emit
keyword and declare the method names as either array or object.
emits: ["toggle-subscribe"];
export default {
name: "TvShow",
props: ["id", "name", "subscribe"],
emits: ["toggle-subscribe"],
methods: {
buttonClick() {
this.$emit("toggle-subscribe", this.id);
},
},
};
You can also declare them as objects:
emits: {
"toggle-subscribe": (showId) => {
if (!showId) {
console.warn("Series id missing!!");
return false;
}
return true;
},
}
In this case, we have a validator to check if the showId
is passed to the toggle-subscribe
method. If not it will throw a warning.
So, the TvShow.vue
file now contains the following code:
// TvShow.vue
<template>
<div>
<h2>{{ name }}</h2>
<button @click="buttonClick">
{{ subscribe ? "Subcribed" : "Not Subscribed" }}
</button>
</div>
</template>
<script>
export default {
name: "TvShow",
props: ["id", "name", "subscribe"],
emits: {
"toggle-subscribe": (showId) => {
if (!showId) {
console.warn("Series id missing!!");
return false;
}
return true;
},
},
methods: {
buttonClick() {
this.$emit("toggle-subscribe", this.id);
},
},
};
</script>
In case, we miss sending the id
in the emit method. The validator will complain. For example,
this.$emit("toggle-subscribe");