Written by Nwose Lotanna✏️
This post will introduce you to how data and its state can be passed from a child component to its parent component in Vue.js using event emitters.
Before you start…
This post is suited for developers of all stages, including beginners. Here are a few things you should already have before going through this article:
- Node.js version 10.x and above installed. You can verify whether you do by running the command below in your terminal/command prompt:
node -v
- A code editor — I highly recommend Visual Studio Code
- Vue’s latest version, installed globally on your machine
- Vue CLI 3.0 installed on your machine. To do this, uninstall the old CLI version first:
npm uninstall -g vue-cli
Then, install the new one:
npm install -g @vue/cli
- Download a Vue starter project here
- Unzip the downloaded project
- Navigate into the unzipped file and run the command to keep all the dependencies up to date:
npm install
Passing data through components
To pass data values from parent components (like the app.vue
) to child components (like nested components) inside the app component, Vue.js provides us with a platform called props. Props can be called custom attributes you can register on a component that let you define data in the parent component, give it a value, and then pass the value to a prop attribute that can then be referenced down in the child components.
This post will show you the reverse of this process. To pass down and update data values in a parent component from the child component in such a way that all other nested components will be updated, too, we use the emit construct to handle event emission and updating of data.
Demo
You will be taken through the process of emitting events from a child component, setting up listening on the parent component in order to pass data from the child component, and then finally updating the data value.
If you followed this post from the start, you will have downloaded and opened the starter project in VS Code. The project is the finished, complete code to this post here.
The reason behind having that as a starter project is so that you can play around with the props concept before getting introduced to reversing the process.
Getting started
In the folder, you will find two child components: test.vue
and test2.vue
, with the parent component being the app.vue
file. We will use the headers of the two child components to illustrate this event emission approach. Your Test.vue
file should look like this:
<template>
<div>
<h1>Vue Top 20 Artists</h1>
<ul>
<li v-for="(artist, x) in artists" :key="x">
<h3>{{artist.name}}</h3>
</li>
</ul>
</div>
</template>
<script>
export default {
name: 'Test',
props: {
artists: {
type: Array
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
li{
height: 40px;
width: 100%;
padding: 15px;
border: 1px solid saddlebrown;
display: flex;
justify-content: center;
align-items: center;
}
a {
color: #42b983;
}
</style>
To make the header receive the title from an implicit definition in the data property section, you create the data section and add the definition, and then add the interpolation symbol in the template, like this:
<template>
<div>
<h1>{{header}}</h1>
<ul>
<li v-for="(artist, x) in artists" :key="x">
<h3>{{artist.name}}</h3>
</li>
</ul>
</div>
</template>
<script>
export default {
name: 'Test',
props: {
artists: {
type: Array
}
},
data() {
return {
header: 'Vue Top Artists'
}
}
}
</script>
If you run the application, you get exactly the same interface you got right at the start. The next step is to change this defined property on click.
Toggling the header
To toggle the header, you will have to add an event listener on click to the header and specify the function that will contain the logic that should happen when it is clicked.
<template>
<div>
<h1 v-on:click="callingFunction">{{header}}</h1>
<ul>
<li v-for="(artist, x) in artists" :key="x">
<h3>{{artist.name}}</h3>
</li>
</ul>
</div>
</template>
<script>
export default {
name: 'Test',
props: {
artists: {
type: Array
}
},
data() {
return {
header: 'Vue Top Artists'
}
},
methods: {
callingFunction(){
this.header = "You clicked on header 1";
}
}
}
</script>
Now your header changes to the string inside the calling function on click.
Setting the emitter
At this stage, you want to pass on this same behavior to the parent component so that on click, every title nested in the parent component changes.
To do this, you will create an emitter that will emit an event in the child component that the parent component can listen to and react (this is just the same as event listener logic for components).
Change the script section in the Test.vue
file to the code block below:
<script>
export default {
name: 'Test',
props: {
artists: {
type: Array
},
header: {
type: String
}
},
data() {
return {
// header: 'Vue Top Artists'
}
},
methods: {
callingFunction(){
// this.header = "You clicked on header 1"
this.$emit('toggle', 'You clicked header 1');
}
}
}
</script>
Here, the type of data expected of the header was defined as a prop. Then, in the method, there is an emit statement that tells Vue to emit an event (just like any other — e.g., a click event) on toggle and pass the string as an argument. This is all you need to set up an event that will be listened to inside another component.
Listening to the emitted event
Now the next thing to do after an event is created is to listen and respond to it. Copy this code block into your app.vue
file:
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<Test v-bind:header="header" v-on:toggle="toggleHeader($event)" />
<Test v-bind:artists="artists" />
<test2 v-bind:header="header"/>
<test2 v-bind:artists="artists" />
</div>
</template>
<script>
import Test from './components/Test.vue'
import Test2 from './components/Test2'
export default {
name: 'app',
components: {
Test, Test2
},
data (){
return {
artists: [
{name: 'Davido', genre: 'afrobeats', country: 'Nigeria'},
{name: 'Burna Boy', genre: 'afrobeats', country: 'Nigeria'},
{name: 'AKA', genre: 'hiphop', country: 'South-Africa'}
],
header: 'Vue Top Artists'
}
},
methods: {
toggleHeader(x){
this.header = x;
}
}
}
</script>
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
In the template section, you can see that the first component, Test
, has two Vue directives on it. The first is the v-bind
, which binds the initial header property to the implicit definition in the data object under the artists array; on initialization, the string Vue Top Artists
is shown.
The second directive is the v-on
, which is for listening to events; the event to listen to is toggle (remember, you already defined it in the Test
component), and the calling function on it is the toggleHeader
. This function is created, and the string from the child component is passed through the $event
argument to display here.
The implication
This passes data through the emitter to the parent component, and so because other components are nested in the parent component, the data in every one of the nested components re-renders and updates. Go into the test2.vue
file and copy this code block into it:
<template>
<div>
<h1>{{header}}</h1>
<ul>
<li v-for="(artist, x) in artists" :key="x">
<h3>{{artist.name}} from {{artist.country}}</h3>
</li>
</ul>
</div>
</template>
<script>
export default {
name: 'Test2',
props: {
artists: {
type: Array
},
header: {
type: String
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
li{
height: 40px;
width: 100%;
padding: 15px;
border: 1px solid saddlebrown;
display: flex;
justify-content: center;
align-items: center;
}
a {
color: #42b983;
}
</style>
Here, the data interpolation was set and specified to be a string in the props
object. Run the application in your development server:
npm run serve
You see that once the event is reacted to in the parent component, all the components updated their header even though the definition was specified in just one child component.
You can find the complete code for this tutorial here on GitHub.
Conclusion
You can see another interesting side of using events in Vue with emitters: you can now create an event in one component and listen to and also react to it in another component. This can have many use cases that will be really beneficial in your workflow — happy hacking!
Editor's note: Seeing something wrong with this post? You can find the correct version here.
Plug: LogRocket, a DVR for web apps
LogRocket is a frontend logging tool that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.
In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.
Try it for free.
The post Modifying component data with event emitters in Vue.js appeared first on LogRocket Blog.
Top comments (0)