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);
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:
Here is another binary tree with more levels:
Now suppose we have these components below nested together:
App
Parent
ChildA
GrandchildA
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
:
ChildA
and GrandchildA
are simple, ChildA
receives the prop and passes it down to GrandchildA
:
And this is what the view looks like at the moment:
Now for some business requirements, users want to change the font size by clicking a button named Change Font Size
in GrandchildA
:
We can do so by utilizing the "$parent" pattern from Part 2:
Line 30 is the "$parent" pattern in action. Click on the button, the font size on the view becomes 20px:
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');
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
:
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:
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:
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:
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
:
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:
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:
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:
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:
In ChildA
, we bind this event onto GrandchildA
, the callback function is ChildA.showYear()
:
Click on the button, the alert window is shown:
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
:
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
:
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)