DEV Community

loading...
Cover image for Call Parents' Methods in Vue! Or literally wherever!

Call Parents' Methods in Vue! Or literally wherever!

Adnan Babakan (he/him)
I'm Adnan Babakan and I'm from Iran. I started programming since I was 8 and now I'm 20. I love programming!
・4 min read

Hi DEV.to community!

When it comes to business logic in every UI framework there are some drawbacks due to their nature and design. With most modern web UI frameworks or libraries such as Vue, React and Angular which utilize components to organize your code, it is fairly easy to call a component's methods/function from a parent by passing them the necessary attributes. But in some cases, the opposite is required as well. Meaning that you might need to call a method defined in your layout or a parent component from a lower levelled component.

In Vue, it is possible to use a state manager such as Vuex to achieve this but there is a fairly simple way built-in Vue as well if you don't want such complex solutions.

Passing to the first parent

If you use your component directly inside another component (such as a page if you are using Nuxt.js) you can $emit an event as shown here.

Given that there is component called MyButton.vue as below:

<template>
  <div>
    <button @click="$emit('my-event')">Click Me!</button>
  </div>
</template>

<script>
export default {

}
</script>
Enter fullscreen mode Exit fullscreen mode

You can see that there is a simple click event that invokes $emit which is a built-in method in Vue.

Of course you can call a custom method and inside that invoke the $emit method as below if you want a more structured component. But if you do so, remember to use this to reference it correctly:

<template>
  <div>
    <button @click="buttonClickHandler">Click Me!</button>
  </div>
</template>

<script>
export default {
  methods: {
    buttonClickHandler() {
      this.$emit('my-event')
    }
  }
}
</script>
Enter fullscreen mode Exit fullscreen mode

No matter how you write this part, it will emit an event. Now when you are using this component you capture this event either by v-on: or @ (which are exactly the same thing) and do whatever you want. For instance, if you want to use this glorious and gorgeous button in your index.vue you can do as bellow:

<template>
  <div>
    <MyButton @my-event="myButtonEventHandler" />
  </div>
</template>

<script>
import MyButton from "~/components/MyButton";

export default {
  components: {
    MyButton
  },
  methods: {
    myButtonEventHandler() {
      alert('I am here!')
    }
  }
}
</script>
Enter fullscreen mode Exit fullscreen mode

As you might've noticed when we emitted in our child component, we've passed an argument that stands for the event's name. And now we are capturing it with the same name. For our case, the name is my-event.

Now if you click on that button, it will fire/emit an event and the parent can capture and use it however it wants. This gives it way more power than just a method inside the child component.

When using the $emit method, as seen above, the first argument is the name of the event and the rest are argument passed to the method. So you can something as below as well:

MyButton.vue:

<template>
  <div>
    <button @click="buttonClickHandler">Click Me!</button>
  </div>
</template>

<script>
export default {
  methods: {
    buttonClickHandler() {
      this.$emit('my-event', 'Hello', 'World')
    }
  }
}
</script>
Enter fullscreen mode Exit fullscreen mode

index.vue:

<template>
  <div>
    <MyButton @my-event="myButtonEventHandler" />
  </div>
</template>

<script>
import MyButton from "~/components/MyButton";

export default {
  components: {
    MyButton
  },
  methods: {
    myButtonEventHandler(x, y) {
      alert(x + ' ' + y)
    }
  }
}
</script>
Enter fullscreen mode Exit fullscreen mode

As you can see, we've passed two extra arguments which they will be passed to the handling method.

Passing to wherever you want!

Now comes the real problem! Imagine you have a very complicated layout with tens of nested components. You cannot just pass them upper and upper one by one! (I mean you technically can but come on!). Here is where you can use $root! Imagine $root as the root of everything (which actually is kind of). No instead of firing and capturing events from child to parent you can fire them and catch them almost everywhere!

The only thing to change while emitting is to emit it on the $root:

MyButton.vue:

<template>
  <div>
    <button @click="buttonClickHandler">Click Me!</button>
  </div>
</template>

<script>
export default {
  methods: {
    buttonClickHandler() {
      this.$root.$emit('a-far-away-event')
    }
  }
}
</script>
Enter fullscreen mode Exit fullscreen mode

Not so much change here. But for capturing it you should listen to it! And that is not hard! On any element that you want to capture this event you should use the $on method on the $root:

index.vue:

<template>
  <div>
    <MyButton />
  </div>
</template>

<script>
import MyButton from "~/components/MyButton";

export default {
  components: {
    MyButton
  },
  mounted() {
    this.$root.$on('a-far-away-event', () => {
      alert('A long way to come!')
    })
  }
}
</script>
Enter fullscreen mode Exit fullscreen mode

The best way to listen is in the mounted() method in my experience. The $on method requires two arguments. The first one being the name of the event and the second one a method to be called when the event is captured.

As well as the previous example you can pass extra arguments when emitting:

MyButton.vue:

<template>
  <div>
    <button @click="buttonClickHandler">Click Me!</button>
  </div>
</template>

<script>
export default {
  methods: {
    buttonClickHandler() {
      this.$root.$emit('a-far-away-event', 'Adnan')
    }
  }
}
</script>
Enter fullscreen mode Exit fullscreen mode

index.vue:

<template>
  <div>
    <MyButton />
  </div>
</template>

<script>
import MyButton from "~/components/MyButton";

export default {
  components: {
    MyButton
  },
  mounted() {
    this.$root.$on('a-far-away-event', (name) => {
      alert('Hello ' + name)
    })
  }
}
</script>
Enter fullscreen mode Exit fullscreen mode

Do not forget that for a better structured component, you should use a Vue method instead of a direct arrow function or a regular function when capturing the event:

index.vue:

<template>
  <div>
    <MyButton />
  </div>
</template>

<script>
import MyButton from "~/components/MyButton";

export default {
  components: {
    MyButton
  },
  mounted() {
    this.$root.$on('a-far-away-event', this.aFarAwayEventHandler)
  },
  methods: {
    aFarAwayEventHandler() {
      alert('Hello ' + name)
    }
  }
}
</script>
Enter fullscreen mode Exit fullscreen mode

This is literally the same functionality but with more organization. And remember not to put parentheses when passing your method as a callback, since if you do it means invoking it and not passing it!

Hope you've enjoyed this little trick!


BTW! Check out my free Node.js Essentials E-book here:

Discussion (0)