How to Modifying component data with event emitters in Vue.js

How to Modifying component data with event emitters in Vue.js
Did you know you can pass data and its state from child components to a parent component in Vue

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 -v
npm uninstall -g vue-cli

Then, install the new one:

npm install -g @vue/cli
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 Testcomponent), 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 propsobject. 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!

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.

Thanks for reading. If you liked this post, share it with all of your programming buddies!

Further reading

☞ Build a Progressive Web App In VueJs

☞ Build a CMS with Laravel and Vue

☞ Beginner’s Guide to Vue.js

☞ Hands-on Vue.js for Beginners

☞ Top 3 Mistakes That Vue.js Developers Make and Should be Avoided

☞ Microfrontends — Connecting JavaScript frameworks together (React, Angular, Vue etc)

☞ Ember.js vs Vue.js - Which is JavaScript Framework Works Better for You

☞ Vue.js Tutorial: Zero to Sixty


Originally published on https://blog.logrocket.com