6

Suppose I have a child component that want to send a message to a great grandparent, the code will be like this, correct me if I'm wrong:

Vue.component('child', {
  template: `<div v-on:click="clicked">Click me</div>`,
  methods: {
    clicked: function () {
        this.$emit('clicked', "hello")
    },
  },
});

Vue.component('parent', {
  template: `<child v-on:clicked="clicked"></child>`,
  methods: {
    clicked: function (msg) {
        this.$emit('clicked', msg)
    },
  },
});


Vue.component('grandparent', {
  template: `<parent v-on:clicked="clicked"></parent>`,
  methods: {
    clicked: function (msg) {
        this.$emit('clicked', msg)
    },
  },
});

Vue.component('greatgrandparent', {
  template: `<grandparent v-on:clicked="clicked"></grandparent>`,
  methods: {
    clicked: function (msg) {
        console.log('message from great grandchild: ' + msg);
    },
  },
});

Is there a possibility to directly intercept the message from the child and call the clicked function in the great-grandparent without the need to set up the passing callback at every parent?

I know I can use a custom databus, https://v2.vuejs.org/v2/guide/components.html#Non-Parent-Child-Communication, but since my components have parent-child relationship already, shouldn't I be able to notify the grandparent in a simpler way?

tony19
  • 125,647
  • 18
  • 229
  • 307
user1506145
  • 5,176
  • 11
  • 46
  • 75

1 Answers1

13

Not if you want to maintain encapsulation. greatgrandparent is not supposed to know about child. It knows about grandparent, but not that there are sub-components or how many. In principle, you can swap one implementation of grandparent out for another that doesn't have multiple layers. Or has even more layers to get to child. And you could put child into a top-level component.

You already know about the notion of a global event bus. A bus doesn't have to be global, though. You can pass it down the props chain. (You could use greatgrandparent itself as the bus, but that would expose it to its children; better hygiene to make a real bus.)

This distinguishes top-level components from sub-components: a sub-component will receive a bus prop to perform the functions of the top-level component that it helps to implement. A top-level component will originate the bus.

Vue.component('child', {
  template: `<div v-on:click="clicked">Click me</div>`,
  props: ['bus'],
  methods: {
    clicked: function() {
      this.bus.$emit('clicked', 'hello');
    },
  },
});

Vue.component('parent', {
  template: `<child :bus="bus"></child>`,
  props: ['bus']
});


Vue.component('grandparent', {
  template: `<parent :bus="bus"></parent>`,
  props: ['bus']
});

Vue.component('greatgrandparent', {
  template: `<grandparent :bus="bus" v-on:clicked="clicked"></grandparent>`,
  data() {
    return {
      bus: new Vue()
    };
  },
  created() {
    this.bus.$on('clicked', this.clicked);
  },
  methods: {
    clicked: function(msg) {
      console.log('message from great grandchild: ' + msg);
    },
  },
});

new Vue({
  el: '#app'
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.2.6/vue.min.js"></script>
<greatgrandparent id="app"></greatgrandparent>
Roy J
  • 42,522
  • 10
  • 78
  • 102
  • 1
    I would recommend to also remove the listener when the state is destroyed, as explained here https://www.digitalocean.com/community/tutorials/vuejs-global-event-bus, otherwise you may get strange bugs if your objects is destroyed and recreated as the listeners of the old object will still be alive! (Just got this annoying bug) To destroy your listener, name your callback function, and then do `beforeDestroy: function() {this.bus.$off('clicked', this.clicked);}` – tobiasBora Oct 09 '20 at 16:37