I prefer to call it the “Conductor” pattern. I think it’s more eloquent and more accurate. A conductor listens to the sections of an orchestra and triggers other sections at the right time. A mediator builds a compromise, by pushing back on opposing forces. The very definition of mediator contains conflict. Our code is harmonious, not in conflict ☮. But the pattern is known as the Mediator pattern. To avoid confusion, let’s stick with that.

Vuex modules will inevitably interact once an application includes enough features. There are 3 ways in which Vuex modules can interact:

An action dispatches an action from another module. An action emits an event. Another module subscribes to the event and dispatches an action. An action emits an event. A mediator subscribes to the event. The mediator dispatches an action from another module.

Option 1 — “Action A” dispatches “Action B”

Using this option looks like the example below. After authenticating, we need to fetch some data.

...

authenticate ({ dispatch }, credentials) {

return callAuthenticationEndpoint(credentials)

.then(result => {

commit('setToken', result.token)

return dispatch('service/fetchData')

.then(() => result)

})

}

Of the 3 options, I would say that option 1 is the most common. It’s the most straight forward, and the only option that doesn’t require an event bus. It aligns best with how we usually think about code, invoking functions from other functions. The problem is that the “authenticate” action doesn’t care about your data needs. Instead of the authenticate action doing one thing well, we now have a side-effect in the action. Now your test for that action also has to account for the side-effect. Notice we also need extra code in order to return the result of authenticating, instead of the fetched data. What about error handling? We have to assume errors are caught in the “fetchData” action, or else catch it here.

Option 2 — Modules listen to modules

// auth module authenticate ({ dispatch }, credentials) {

return callAuthenticationEndpoint(credentials)

.then(result => {

commit('setToken', result.token)

eventBus.emit('authenticated', result)

return result

})

} // service module initialize ({ dispatch }) {

eventBus.on('authenticated', () => dispatch('fetchData'))

}

fetchData () {

}

Option 2 is a little better, but it involves a lot of overhead. You have to import an event bus into each module, and emit an event from the action. It also requires some sort of initialize action, because once we’re in a module file, we don’t have access to the dispatch method except from within another action. So somewhere else in the code, this initialize action needs to be dispatched.

If at this point you’re asking, “what if I move the event listener registrations to another file,” well, you’ve discovered the Mediator pattern, or you already knew about the Mediator pattern.

Option 3 — The Mediator pattern

// auth module authenticate ({ dispatch }, credentials) {

return callAuthenticationEndpoint(credentials)

.then(result => {

commit('setToken', result.token)

eventBus.emit('authenticated', result)

return result

})

} // service module fetchData () {

} // mediator file eventBus.on(

'authenticated',

() => store.dispatch('anotherModule/fetchData')

)

Now that there’s a mediator, modules no longer need an initialization action to register event listeners. It’s a little difficult to see the effect from this small example, but we’ve decoupled these modules, mostly. The service module will still be coupled to the auth module because it needs the token to authenticate requests, but that’s circumstantial. Often, by using the Mediator pattern, you can completely decouple your modules.

There’s still additional overhead. Modules are decoupled from each other, but each module is coupled to the event bus, so if you are able to reuse a module in another app, you’ll also need the event bus. I would say that’s OK, since it enforces a useful pattern, but in Vuex, we can have our cake and eat it too, by using Vuex as the event bus.

Use Vuex as the event bus

Vuex’s API includes:

store.subscribeAction— listen to actions store.subscribe — listen to mutations

These APIs don’t allow you to specify which action you want to listen to. Instead, you’ll need to setup a switch statement inside the handler to branch according to the action type.

// store-moderator.js export default function configureModerator (store, router) {

// listen to mutations

store.subscribe(({ type, payload }, state) => {

switch (type) {

case 'auth/setToken':

return store.dispatch('service/fetchData')

}

}) // listen to actions

// note: doesn't not wait for the result of async actions

store.subscribeAction(({ type, payload }, state) => {

switch (type) {

case 'auth/signOut': return router.push('/auth/signin')

}

})

}

When I can, I try to listen to mutations instead of actions. Usually, an action will result in a mutation. By listening to the mutation, you can tell when the action has ended. If you listen to the action, the handler is called right after the action is called, before it’s had a chance to finish.

There’s no need now for an event bus inside of the module, since Vuex is the event bus. If we look at our modules, they are completely decoupled from each other, as well as decoupled from an external event bus.