Hot questions for Using EventBus in vue router

Question:

I used an EventBus to pass the UserId to another component (UpdateStaff). Then, I want to call an API at url/api/staffs/{UserId}, to get the data of that particular user and bind the data to the input fields. I'm able to get the response payload but unable to bind to the input fields.

UpdateStaff.vue:

An example of an input field in the template:

<input class="input" type="text" placeholder="Username" v-model="data.username">

Data properties & created method:

data () {
  return {
    data: {
      name: '',
      username: ''
    }
  }
},
created () {
  EventBus.$on('getUserId', (userId) => {
    axios.get(staffUrl + '/' + userId)
      .then((response) => {
        let self = this
        self.data.username = response.data.username
        self.data.name = response.data.name

        console.log(self.data.username)
        // It prints out exactly what i want
        // BUT it doesn't show the data in the input field....?
      })
      .catch((error) => {
        console.log(error)
      })
  })
}

Staff.vue:

Has a vuetable component in it to display all Staffs. After clicking the Update Button, I redirect to the UpdateStaff page via calling an onActions method.

methods: {
  onActions (action, data) {
    router.push({ path: '/user/UpdateStaff' })
    EventBus.$emit('getUserId', action.data.userId)
  }
}

Previously, I was able to display data in input fields with just axios alone. However, after adding the EventBus with axios in it, I was unable to display data in input fields anymore.

How should I go about fixing this issue?


Answer:

The reason you're not seeing the data update correctly is that this is not the Vue instance when you reference it in the .then callback.

Put the let self = this statement at the very beginning of your created hook to be sure you are storing the correct reference to this:

created () {
  let self = this
  // after this point, `self` will reference the Vue instance even in callbacks

  EventBus.$on('getUserId', (userId) => {
    axios.get(staffUrl + '/' + userId)
      .then((response) => {
        // setting `self` to `this` here doesn't make sense because `this` 
        // is not refering to the Vue instance in this callback

        self.data.username = response.data.username
        self.data.name = response.data.name
      })
      .catch((error) => {
        console.log(error)
      })
  })
}

The reason the getUserId event isn't being handled when you push to that route is that the $emit event is being fired before the UpdateStaff component is created (meaning the $on handler hasn't been set yet).

To wait for the component to have been created, wrap the $emit in a this.$nextTick callback:

methods: {
  onActions (action, data) {
    router.push({ path: '/user/UpdateStaff' })
    this.$nextTick(() => {
      EventBus.$emit('getUserId', action.data.userId)
    })
  }
}

From the documentation on the usage of Vue.nextTick:

Defer the callback to be executed after the next DOM update cycle. Use it immediately after you’ve changed some data to wait for the DOM update.

Question:

I'm using Vue on a Node/Webpack/Vue Router environment and trying to setup a global event handler or bus. But it's not working. It's showing up as undefined. Here's my setup:

main.js:

//Declare event handler
Object.defineProperty(Vue.prototype, '$bus', {
  get () {
    return this.$root.bus
  }
})

//Declare app instance
new Vue({
  el: '#app',
  router,
  components: { App },
  template: '<App/>'
})

App.vue

<script>
export default {
  name: 'App'
  created: () => {
    console.log(this.$bus);
  }
}
</script>

The console.log statement returns undefined, meaning the event handler somehow isn't getting passed to the app. I've also tried the following statement to declare the event handler:

main.js

//Declare event handler
const eventBus = new Vue()
Vue.prototype.$bus = eventBus

But that doesn't work either.


Answer:

This is a problem with using the arrow function. While the () => {} syntax looks nicer than function() {}, there is a difference, in that the the arrow function uses a lexical context for this (from where it was defined, instead of from where it was called, which id what you need in this instance), meaning that this is no longer the Vue instance, so you cannot use this.$bus. You can fix this by replacing the arrow function with a regular function using either created: function() {... or the concise (but functionally equivalent) version created() {...

You can read up more on the differences by looking up articles on es6, arrow functions, lexical scope, this context.

main.js code:

import Vue from "vue";
import App from "./App";

Vue.prototype.$bus = new Vue();

new Vue({
  el: "#app",
  components: { App },
  template: "<App/>"
});

somewhere in app.js

export default {
  name: "App",
  created() {
    console.log(this.$bus);
  }
};