In any application data sharing among various components/services are key to maintain data flow. In vue, different data sharing methods are:
Props: Allow to pass data from parent to child component.
Emit: Allow to pass data from child to parent component.
Vuex Store: Allow to share data across components.
Apart from above three, there is an another technique to share data across the components is, Event Bus which works on pub-sub model.
Event Bus technique in vue, emits an event from one components (publisher) and other components (subscribers) will listen to the event in real time and act.
The event bus implementation in vue is a global instance like vuex store. Lets see this by an example on How to implement it.
In this example I’m using Vue@2.7.0 and mitt@2.1.0. mitt. mitt is a javascript library helps to achieve event bus implementation.
Implementation
Step 1: First we will be creating a vuejs application and then create an global instance of event bus using mitt. In my case I created a folder named ‘eventbus’ and add the index.ts file. eventbus => index.ts
import mitt from 'mitt'; export default mitt();
Step 2: Now adds three different components, where first component will emit (publish) an event to pass the message and other two components will listen the event and display the message.
Above component has a input control to accept the user input and publish this message through emitting an event ‘getMessage’ on click of publish button here.
If you haven’t read through my previous article here onprops vs emit in vue.js, I would recommend to read it first unless you are aware of props and emit concept of vuejs framework. I also expects, you know the basics already about Vue framework.
So far we have seen the ways to share/transfer data i. using props : from parent to child. ii. using emit: from child to parent. but what if we have to share the data across our vuejs application: iii. this is where Vuex Store is used.
What is Vuex Store
Vuex Store is basically a container to hold your application’s state information which will be available throughout the application. Vuex store are reactive, hence if store’s state changes from any component, it will be updated across component reactively.
Vuex Store uses a single state tree, it means a single object will contain entire application level state. In a big project we would generally need to maintain the state module wise hence here we will be going through an example of creating a custom vuex store submodule to achieve the same.
Vuex Store state update happens through Vuex mutation but you cannot mutate the store state directly instead you have to commit your mutations to update state hence we nee to use action here to commit mutations.
Vuex mutations are very similar to events: each mutation has a string type and a handler. The handler function is where we perform actual state modifications, and it will receive the state as the first argument and payload (data) is the second argument. This is how vuex store is operated to modify and update the state.
Example Scenario
For an example here, we will be creating a custom store submodule to maintain user’s state which will have it’s own mutations, actions and getters to read/write it. Our sub module will be divided into for parts: i. State: state of the sub module ii. Getters: gettters to read the state of the sub module. iii. actions: action to commit the state mutation iv. mutations: mutate the state.
Action Time: let’s code.
First we need to create a vue js application and install Vuex package. Store is available from Vuex. In my case I created an vuejs application named “VueJsDemoApp” using the dependencies as of vue@2.7.0 and vuex@3.6.2 for the example here.
Below are the steps we will be following to create the custom vuex store to maintain user state (first name, last name and email). We will call this user state as submodule as vuex store maintain single tree and that is the main module and here we would create a custom vuex sub module and add to the vuex store. Steps are:
Step 1: Inside src folder create a folder name ‘interfaces’ to define interfaces common to entire application. In our case, we will be creating a interface type IUser. Hence add two files here as src=> interfaces => i-user.ts
You may define the interface types into index.ts file only but I did this to separate the interface definition from index.ts file. This will be very useful and needed when you have multiple interface types and you want to maintain the code clean.
Note: index.ts file is the file which vue framework will look from for imports.
Step 2: within src folder create a folder with name as ‘store’. All our code related to store module will go here.
Step 3: Within store folder create three folders named as ‘config’, ‘interfaces’ and ‘modules’.
Step 4: within interfaces folder, create an generic interface called IRootState.
src=> store => interfaces => i-root-state.ts : which defines the IRootState interface.
// our root state is empty because we make use of modules, // however we do need it for generic arguments and thus we declare it export default interface IRootState { };
and src=> store => interfaces =>index.ts : to export the type
export { default as IRootState } from './i-root-state';
Step 5: create the generic store options config in index.ts file inside config folder as:
Step 6: Create the index.ts inside store folder to define the store object which is based on our generic state type as IRootState.
src=> store => index.ts
import Vue from 'vue'; import Vuex from 'vuex'; import config from './config'; import { IRootState } from './interfaces';Vue.use(Vuex);const store = new Vuex.Store<IRootState>(config);export default store;
Step 7: Import the store module into the App.vue (application).
<template> <div id="app"> <Home/> <StoreData/> </div> </template><script lang="ts"> import Home from './components/Home.vue'; import StoreData from './components/StoreData.vue' import store from "@/store";export default { components: { Home, StoreData }, store } </script><style> </style>
As of now, you can focus on highlighted code in bold only and rest you will understand when we create defined components late in this example, but this will be our full code for App.Vue.
Now will create our submodules (i.e. user in this case) inside modules folder.
Step 8: Create a ‘user’ sub module by creating a folder name ‘user’ inside modules folder.
Step 9: Within user folder create a folder called ‘static’ (you can name it something else if you want)where we define IUserState interface type to hold IUser as state and enums to avoid hard coded names for action, mutations and getters etc. Add the respective files inside src=> store => modules=> user => static as:
import IUser from "@/interfaces/i-user";export type IGlobalUserPayload = Partial<IUser>;
Note: State payload type created as Partial<IUser> so that the state doesn’t force to update all the properties of IUser always instead you can commit partial update of IUser too.
state type object which will available in the store state tree.
vii. index.ts
export { default as GlobalUserActionEnum} from './global-user-action-enum'; export { default as GlobalUserMutationEnum } from './global-user-mutation-enum'; export { default as GlobalUserGetterEnum} from './global-user-getters-enum'; export { NAMESPACE } from './global-user-namespace'; export {default as IUSerState } from './i-state-user';
export all types defined above inside user =>static folder.
Step 11: Define the mutations to save the user state in the store
src=> store => modules=> user => mutations.ts
import { MutationTree } from 'vuex'; import { GlobalUserMutationEnum } from './static'; import IUserState from "./static/i-state-user"; import { IGlobalUserPayload } from './static/global-user-payload';export const userMutations: MutationTree<IUserState> = { [GlobalUserMutationEnum.Mutate_USER](state, payload: IGlobalUserPayload) { Object.assign(state.user, payload); }, };export default userMutations;
Step 12: Define the action to commit the mutations
src=> store => modules=> user => action.ts
import { ActionTree } from 'vuex'; import { GlobalUserActionEnum, GlobalUserMutationEnum } from './static'; import IUserState from './static/i-state-user'; import { IRootState } from '../../interfaces'; import { IGlobalUserPayload } from './static/global-user-payload';export const userActions: ActionTree<IUserState, IRootState> = {[GlobalUserActionEnum.SET_USER]({ commit }, payload: IGlobalUserPayload) { commit(GlobalUserMutationEnum.Mutate_USER, payload); }, };export default userActions;
Step 13: Define getters to read the IUser from the store state of user module.
src=> store => modules=> user => getters.ts
import { GetterTree } from 'vuex'; import { IRootState } from '../../interfaces'; import IUser from "@/interfaces/i-user"; import IUserState from './static/i-state-user'; import GlobalUserGetterEnum from './static/global-user-getters-enum';export const userGetters: GetterTree<IUserState, IRootState> = {[GlobalUserGetterEnum.GET_USER](state): IUser { return state.user; }, };export default userGetters;
Step 14: Define the user state module
src=> store => modules=> user => index.ts
import { Module } from 'vuex'; import { IRootState } from '../../interfaces'; import IUserState from "./static/i-state-user"; import { userGetters } from './getters'; import userMutations from './mutations'; import { userActions } from './action'; import { userState } from './state';const globalUser: Module<IUserState, IRootState> = {namespaced: true, state: userState, getters: userGetters, mutations: userMutations, actions: userActions, };export default globalUser;
Step 15: Finally export the module. Create a index.ts file inside modules folder.
src=> store => modules=> index.ts
import user from './user'export default{ user };
and we are done. Our user store submodule is ready to be used and consumed. To demonstrate the consumption I’m creating two components inside components folder here as:
src => components => Home.vue
<template> <div class="home"> First Name: <input v-model="user.firstname" /> <br /> Last Name: <input v-model="user.lastname" /> <br /> Email: <input v-model="user.email" /> <br /> <button @click="onSave()">Update User in Store</button> <br /> </div> </template><script lang="ts"> import Vue from "vue"; import { IUser } from "@/interfaces"; import { mapActions, mapGetters } from "vuex"; import { GlobalUserActionEnum, GlobalUserGetterEnum, NAMESPACE, } from "../store/modules/user/static";export default Vue.component('Home', {
Here we are using mapGetters and mapActions feature of vuex to map the action and getters of our user store submodule which is mapped with the action and getters using the enum as we defined earlier to avoid hardcoded names.
As per this component here, there are three text boxes which will take user’s first name, last name and email as input and update the same to store on click of the button.
As we learn, Vuex is reactive in nature hence to demonstrate this let’s create another component as:
src=> components => StoreData.vue
<template> <div> <br /><br /> <h1>User Data (update on click) : {{ userData }}</h1> <h1>User Data (reactive update) : {{ userComputedData }}</h1> <button @click="onFetch()">Read User from Store</button> <br /> </div> </template><script lang="ts"> import Vue from "vue"; import { mapGetters } from "vuex"; import { GlobalUserGetterEnum, NAMESPACE, } from "../store/modules/user/static";export default Vue.component('StoreData', {
Here, I have two h1 tags where first one display the user info from store by reading it through getters map whereas the second h1 tag display/updates automatically due to reactive action as soon as store is update for user.
If you run the application now, you will see the UI output as:
In above screen shot other half (right side) of the picture is of vue.js dev tools extension of chrome browser which is a great tool for vuejs development. Download and enjoy.
Note: with this approach you will be able to create as many store submodules you want. Simple approach would be to just copy paste the entire user folder and change the name as of your module name.