8
votes

There seems to be a lot of discussion around this topic such as Stackoverflow answer using hub, Stackoverflow answer using refs, so I really like to ask experts to provide for once a clear concise answer to this question. If the answer is also just not possible please state that!

Here is the scenario: There are two components, a parent and a child

<Parent> // There is a button here that can be clicked to emit an event using 'this.$emit()'
   <Child></Child> // The child listens and once hears the event, it does something
</Parent>

What to be achieved?

Clicking the button in the Parent emits a certain event, the child will be constantly listening and once it hears the event it executes an action, such as calling a method of its own.

What is out there about this so far?

  1. Using a hub, in Vue Hub it is clearly stated this is for Non Parent-Child Communication, so what is the point in using it for a parent-child communication?

  2. Using Refs, which is given as an end solution when it is not possible to use props and events. So why it is not possible with events at first place?

My own thought

It seems to me the firing of an event and listening to it is only possible from child to parent, basically one way communication. The parent is able to emit an event but child component(s) are not able to capture the event. Why? I tried this and didn’t work:

In the parent component I have (triggered by clicking a button in the parent component):

methods: {
  generateCharts: function () {
    this.$emit('generate-charts')
    console.log('charts generated')
}

In the child component I have:

mounted () {
 this.parent.$on('generate-charts', function () {
   console.log('event captured') // Here nothing gets logged to the console
 })
}

Update

Just came across this answer Vue $emit. Apparently this is not possible at all with Vue.

At first instance it seems it is a deficiency because I have been in several situations where I needed to fire an event from parent and listen to it in the child.

I can imagine there must be a reason why this is not possible with Vue, it is probably a design consideration, Vue experts explaining why this is the case, and what is the better design approach to solve in general a scenario to pass events from parent to child, would be very appreciated.

1
I would have solved it by passing a prop to the child generateCharts: true and make a computed property in the child like generatedCharts() { if(this.generateCharts) {...} }. However I would also like to know how experts would solve that. - Crackers
If you could give us a real problem you're trying to fix, we might be able to do something. You haven't convinced me that vue is missing anything vital. - bbsimonbb
@user1585345 this is my situation link, in the real application, each child component represents a chart, and each child component has its own generateChart method that retrieves and transforms the data for that component. So my goal is to click on button and update all chart components at once. That is why I needed the events. - Payam Mesgari

1 Answers

3
votes

The answer is to use props and react to changes to those props. It is a little confusing to get used to at first because it seems like a lot of code to to do something simple but as your application gets more complex, the one way data flow enforced by the use of props really helps with debugging and reasoning about what the code is trying to accomplish.

For instance, a modal. Your parent sets showChildModal = true with a button click, this value is passed to the child as a prop. The child is watching for changes to the prop, when it sees it set to true it opens the modal. Finally, when the modal is closed, the child $emit('close') which the parent is watching for, and when it sees that it sets showChildModal = false

Vue.component('child', {
  template: '#child',
  props: ['showModal'],
  name: 'child',
  watch: {
    showModal: function(show) {
      if (show) {
        window.setTimeout(() => {
          if (window.confirm("Hi, I'm the child modal")) {
            this.$emit('close');
          } else {
            this.$emit('close');
          }
        }, 100)
      }
    }
  }
})

var vm = new Vue({
  el: '#el',
  data() {
    return {
      showChildModal: false
    }
  }
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div id="el">
  Parent
  <p>
    <button @click="showChildModal = true">Click to show child modal</button>
  </p>
  <hr>
  <p>
    <child :show-modal="showChildModal" v-on:close="showChildModal = false"> </child>
  </p>
</div>

<script type="x-template" id="child">
  <div>
    Child
    <p>
      modal open? {{ showModal }}
    </p>
  </div>
</script>