DEV Community

Cover image for Vue.js Components Communication Patterns (without Vuex) - Part 3
Chris Y.
Chris Y.

Posted on

Vue.js Components Communication Patterns (without Vuex) - Part 3

Upward-broadcast and Downward-broadcast patterns

In Part 2 of this series, we used the "$parent" and the "$children" patterns to enable components to communicate in a three-level hierarchy. Towards the end, we had a question in mind for an even more complex hierarchy of components, e.g. what if there are ten levels of components? Do we need to do something like:

this.$parent.$parent.$parent.$parent.$emit('anyEvent', args);
Enter fullscreen mode Exit fullscreen mode

Let alone the children components can be hard to track.

This leads to the pattern to be introduced here in Part 3.

First, all the example components we have worked through form a data structure called Binary Tree. Below is the graphic from the Vue documentation for nested components in the computer memory when a Vue app is running:

Image description

Here is another binary tree with more levels:

Image description

Now suppose we have these components below nested together:

App

Parent

ChildA

GrandchildA



Image description

In App, we bind an event named change:font onto Parent, the callback function is handleChangeFont which will update the font size for Parent and its descendants, according to the argument size passed in.

Parent is the same as previous examples, it maintains a list of desserts and passes it to ChildA:

Image description

ChildA and GrandchildA are simple, ChildA receives the prop and passes it down to GrandchildA:

Image description

Image description

And this is what the view looks like at the moment:

Image description

Now for some business requirements, users want to change the font size by clicking a button named Change Font Size in GrandchildA:

Image description

We can do so by utilizing the "$parent" pattern from Part 2:

Image description

Line 30 is the "$parent" pattern in action. Click on the button, the font size on the view becomes 20px:

Image description

Awesome!

But as you can see, with the complexity of the nesting, we would have to code something like:

      this.$parent.$parent.$parent.$parent.$parent.$emit('change:font', '20px');
Enter fullscreen mode Exit fullscreen mode

if users want to add that button in a grand-grand-grand-grandchild component.

A better solution in a large tree structure would come in handy.

Upward broadcast pattern

If a component wants to inform an ancestor component via triggering one of its events, this component can literally "broadcast" upward to all its ancestors about an event it wants to trigger, and if one ancestor component has that specific event registered, it will perform the callback function automatically. We can implement this event mechanism on the Vue prototype object, so all the VueComponent instances can have access to it via this.

In main.js, let's create a function named $upwardBroadcast on Vue.prototype:

Image description

The $upwardBroadcast function has two parameters:

  • event: the event being broadcast upward from the current component
  • args: the data being passed when emitting the event

It will broadcast an event from the current component upward to all the ancestors, if one ancestor in the upward tree hierarchy has that event registered, it will respond and execute the callback function registered alone with the event. Let's implement it:

Image description

First, at line 12, we save the parent of the current component. In lines 12 - 16, if the parent exists, it will use the parent instance to emit the event, then proceed to the parent's parent, and the parent's parent's parent, etc.
The while loop stops when there is no parent anymore, meaning it has reached the top (root) node on the tree.

Now let's see how to use it to improve the previous "$parent" pattern in GrandchildA. Very simple, just one line of change:

Image description

Line 31 replaces line 30 and uses the $upwardBroadcast function via this, and it broadcasts the event change:font and passes the argument '20px'. If we click the button, the font size changes as before:

Image description

Special Note

Here I say "uses the $upwardBroadcast function via this", not "on" this, because $upwardBroadcast is not defined on the VueComponent instance created from the VueComponent constructor function, but on the Vue constructor's prototype - as what we did in main.js. Yes, a better understanding of Vue.js requires a solid foundation of JavaScript basics, that's why I like Vue so much - you are not just using a framework to do the work, but you get to consolidate and deepen basic knowledge of JavaScript.

But if you think about it a bit - how come a VueComponent instance can access the Vue constructor's prototype?Actually, Vue did one thing on top of the JavaScript prototype chain - it modified where VueComponent.prototype points.

Also, the function name starts with a $ sign, and this is only because this is the convention of all the built-in properties and methods in Vue.js.

Downward broadcast pattern

Now let's implement a downward broadcast mechanism. In main.js, let's create another function named $downwardBroadcast on Vue.prototype:

Image description

It has the same two parameters as $upwardBroadcast, and it will broadcast an event from the current component downward to all the descendants, if one descendant in the downward tree hierarchy has that event registered, it will respond and execute the callback function. We can do so:

Image description

First, we get all the descendants of the current component, and for each child, it will emit the event. Here what is different from one child only having one parent in $upwardBroadcast, is that now each child can have many children, so if there are any children components of a current child, we need to repeat the same logic, as seen in line 28.

This is the perfect case for recursion, and let's implement it:

Image description

In the function body, we create another function called downwardBroadcast. First, we execute this function by passing in the current component's this.$children array, as seen in line 33. Then within downwardBroadcast, we loop through the children array, and if there are children under the current child, we will execute downwardBroadcast again, passing in the current child's $children.

Now our main.js looks like this:

Image description

Time to see it in action. We will downward broadcast an event named show:year in App to all its descendants after clicking a new button named Display current year, and the argument passed in is the current year:

Image description

Image description

In ChildA, we bind this event onto GrandchildA, the callback function is ChildA.showYear():

Image description

Click on the button, the alert window is shown:

Image description

The broadcasting is powerful, isn't it?

Encapsulate the functions ( Hooks / Composition style)

One thing we can improve is to move the functions in main.js into a separate file - src/hooks/events.js,
so this file contains functions that enhance the event system on Vue.prototype:

Image description

Following the naming convention of Hooks or Composition API, we create two new functions named useUpwardBroadcast and useDownwardBroadcast, the parameter is the Vue constructor function. Within each function body, it is the previous function defined.

Now in main.js:

Image description

we can import these two functions and run them to enhance Vue.prototype, if we need.

In the next part of this series, we will explore another mighty Vue.js components pattern.

Here are all the articles in this series:

Vue.js Components Communication Patterns (without Vuex) - Part 1

Vue.js Components Communication Patterns (without Vuex) - Part 2

Vue.js Components Communication Patterns (without Vuex) - Part 3

Vue.js Components Communication Patterns (without Vuex) - Part 4

Vue.js Components Communication Patterns (without Vuex) - Part 5

Vue.js Components Communication Patterns (without Vuex) - Part 6

Vue.js Components Communication Patterns (without Vuex) - Part 7

Top comments (0)