Vue Emitter Design Pattern: Broadcast and Dispatch
The Vue Emitter Design Pattern is the all-around solution for programatic event-based child-parent communication
Working with events in Vue.js works great out of the box, but what happens if you want to send an event to a specific parent or child component?
The Vue Emitter Design Pattern is the all-around solution for programatic event-based child-parent communication. To provide this functionality, we're going to implement two essential methods: broadcast
and dispatch
.
Broadcast
Broadcasting means emitting an event from a parent component to all child components that have a specific name. This is useful when you want to update several child components.
To implement broadcasting, we're going to recursively descend the children of the parent component. In order for it to be easy to reuse, we'll write a Vue mixin inside a BroadcastMixin.vue
file:
/**
* Broadcast an event with given params to specific child properties
*
* @param componentName
* @param eventName
* @param params
*/
function broadcast (componentName, eventName, params) {
this.$children.forEach(child => {
const name = child.$options.name;
if (name === componentName) {
child.$emit.apply(child, [eventName].concat(params));
} else {
broadcast.apply(child, [componentName, eventName].concat([params]));
}
});
}
export default {
methods: {
broadcast (componentName, eventName, params) {
broadcast.call(this, componentName, eventName, params);
}
}
}
With the code being written as a Vue mixin, using it is as simple as:
import BroadcastMixin from './mixins/BroadcastMixin.vue';
export default {
name: 'ParentComponent',
mixins: [
BroadcastMixin
],
methods: {
broadcastSomething() {
this.broadcast('ChildComponent', 'input', 42);
}
}
}
Dispatch
Dispatching means emitting an event from a child component to a parent component that has a specific name. This is useful in scenarios where the parent needs to be notified of something happening in a child component.
To implement dispatching, we're going to recursively ascend the parents of the child component until we find the one we're looking for. Again, we'll be using a Vue mixin in order to have reusable code. Here's the DispatchMixin.vue
file:
/**
* Dispatch an event from child to parents of given type
*
* @param componentName
* @param eventName
* @param params
*/
function dispatch (componentName, eventName, params) {
let parent = this.$parent || this.$root;
let name = parent.$options.name;
while (parent && (!name || name !== componentName)) {
parent = parent.$parent;
if (parent) {
name = parent.$options.name;
}
}
if (parent) {
parent.$emit.apply(parent, [eventName].concat(params));
}
}
export default {
methods: {
dispatch (componentName, eventName, params) {
dispatch.call(this, componentName, eventName, params);
}
}
}
To use the above Vue mixin, import it and add it to the mixins array:
import DispatchMixin from './mixins/DispatchMixin.vue';
export default {
name: 'ChildComponent',
mixins: [
DispatchMixin
],
methods: {
dispatchSomething() {
this.dispatch('ParentComponent', 'input', 42);
}
}
}
Conclusions
I've used this design pattern quite a lot in Inkline, my Vue.js UI/UX Framework. Make sure you check it out if you haven't already.
An use case where I found it to be of tremendous importance was reinitializing an array of values based on child components. To make sure that the array data is always in sync, child components would call a synchronize
event whenever they would get mounted or destroyed.
The Vue Emitter Design Pattern will prove itself particularly useful when creating reusable related components that can have any level of nesting. I'd love to hear your thoughts down below.