Picture of Brian Love wearing black against a dark wall in Portland, OR.

Brian Love

Vuex for NgRx Devs

Learn to build manage state in a Vue.js application using Vuex from the perspective of an Angular dev using NgRx.

Download

In this post I’ll be working with a demo Vue.js application. You can clone the repository or download a zip file of the source code:

Previously

Previously, I wrote about my experience of building a simple to-do list style application using Vue.js from the perspective of an Angular developer. In this post, I’ll continue by using the Vuex library to manage the state of the application.

NgRx is a popular library for managing state in an Angular application. As such, I’ll be writing this post from the perspective of using NgRx. FWIW, NgRx is inspired by the Redux library that is popular for managing state in a React application. I’ve also used Redux and find it to be easy to use and powerful.

Let’s dive in.

Installation

$ npm install vuex --save
$ yarn add vuex

Next, create a new src/store directory and a new src/store/index.js file:

$ mkdir src/store
$ touch src/store/index.js

Open the src/store/index.js file and invoke the Vue.use() method to use vuex:

import Vue from 'vue';
import Vuex from 'vuex';

Vue.use(Vuex);

Determine Actions

When working with NgRx, or Redux, or Vuex, we want to start by planning out the actions that will need to be dispatched to the store to mutate the state of our application.

Here are the actions that we will likely need for a to-do application:

Mutations

To get started, let’s define the mutation constants. Create a new src/store/mutation.js file.

Define string constants for each action that will result in a mutation to the state of the application:

export const ADD_TO_DO = 'addTodo';
export const CLEAR_TO_DOS = 'clearTodos';
export const COMPLETE_TO_DO = 'completeTodo';
export const INCOMPLETE_TO_DO = 'incompleteTodo';
export const REMOVE_TO_DO = 'removeTodo';

When using NgRx it is common to define an enum for the “action types” in our application. So, this should look pretty familiar. In fact, before TypeScript 2.4 supported string enums, we often defined the action types when using NgRx by using string constants similar to how we have defined the mutation types in our Vue application.

Now we are ready to define the mutations object, which we will export so we can wire up our mutations to the Vuex store. Mutations are the same concept as using reducer() pure functions with NgRx that would be added to the ActionReducerMap.

export const mutations = {
  [ADD_TO_DO]: function (state, todo) {
    todo.id = Math.random();
    if (!todo.complete) {
      todo.complete = false;
    }
    state.todos.push(todo);
  },

  [CLEAR_TO_DOS]: function (state) {
    state.todos = [];
  },

  [COMPLETE_TO_DO]: function (state, todo) {
    todo.complete = true;
  },

  [INCOMPLETE_TO_DO]: function (state, todo) {
    todo.complete = false;
  },

  [REMOVE_TO_DO]: function (state, todo) {
    const i = state.todos.map((todo) => todo.id).indexOf(todo.id);
    if (i === -1) {
      return;
    }
    state.todos.splice(i, 1);
  },
};

Using es2015 object initializer spec for computed properties we declare properties for each action in the mutations object.

Each handler function receives the state followed by an optional payload that is dispatched with the action.

Actions

Next, let’s define the actions. Create a new src/store/actions.js file:

import {
  ADD_TO_DO,
  CLEAR_TO_DOS,
  COMPLETE_TO_DO,
  INCOMPLETE_TO_DO,
  REMOVE_TO_DO,
} from './mutations';

export default {
  addTodo({ commit }, todo) {
    commit(ADD_TO_DO, todo);
  },

  clearTodos({ commit }) {
    commit(CLEAR_TO_DOS);
  },

  completeTodo({ commit }, todo) {
    commit(COMPLETE_TO_DO, todo);
  },

  incompleteTodo({ commit }, todo) {
    commit(INCOMPLETE_TO_DO, todo);
  },

  removeTodo({ commit }, todo) {
    commit(REMOVE_TO_DO, todo);
  },
};

We define the actions as functions, which accept the context object along with an optional payload. Note that we’re using destructuring assignment in the first argument to each action in order to define a local-scoped variable based on the property in the object.

In our case, we are only concerned with the commit property. The commit instance method enables us to commit a mutation to the store. The first argument is the type and the second argument is the action’s payload. This is the same concept derived from Redux that is used by NgRx.

Create a new Store()

Open the src/store/index.js file and add the following to create a new Vuex.store() instance:

import actions from './actions';
import { mutations } from './mutations';

export default new Vuex.Store({
  strict: true,
  state: {
    todos: [],
  },
  actions,
  mutations,
});

We export the default Vuex.store() instance, specifying the following options:

Inject Store

With our actions and mutations defined and the store wired up, our next task is to inject the store into all of the components in our application. We’ll do this via the Vue.use() method that was defined and exported in the src/state/index.js module.

Open src/main.js and specify the store in the new Vue() constructor:

import store from './store';

new Vue({
  store,
  render: (h) => h(App),
}).$mount('#app');

Dispatch Actions

With our store ready to go let’s dispatch() actions from our App. Open src/App.vue and implement the

<script>
import TodoForm from '@/components/TodoForm.vue';
import TodoList from '@/components/TodoList.vue';
import { ADD_TO_DO, COMPLETE_TO_DO, INCOMPLETE_TO_DO } from './mutations';

export default {
  name: 'App',
  data() {
    return {
      fixed: false,
      todo: {
        task: null
      }
    };
  },
  components: {
    TodoForm,
    TodoList
  },
  computed: {
    todos() {
      return [
        {
          task: 'Watch Ozark Season 2',
          complete: false
        },
        {
          task: 'Use Vuex in my awesome app',
          complete: true
        }
      ];
    },
    completeTodos() {
      return this.todos.filter(todo => todo.complete);
    },
    incompleteTodos() {
      return this.todos.filter(todo => !todo.complete);
    }
  },
  methods: {
    onAdd: function() {
      this.$store.dispatch(ADD_TO_DO, this.todo);
    },
    onChange(todo) {
      if (todo.complete) {
        this.$store.dispatch(COMPLETE_TO_DO, todo);
      } else {
        this.$store.dispatch(INCOMPLETE_TO_DO, todo);
      }
    }
  }
};
</script>

Note that we use the $store property within the component that is injected into our component. Invoke the dispatch() method to dispatch an action to the store, first specifying the action type along with an optional payload.

Devtools

Using the vue-devtools we can inspect the state of our application and observe the actions, and optional payloads associated with the action:

Incomplete To Dos

Conclusion

Creating apps using Vue with the Vuex state management pattern + library is easy, robust and very similar to building apps with Angular and NgRx.

\n```\n\nNote that we use the `$store` property within the component that is injected into our component.\nInvoke the `dispatch()` method to dispatch an action to the store, first specifying the action `type` along with an optional `payload`.\n\n## Devtools\n\nUsing the [vue-devtools](https://github.com/vuejs/vue-devtools) we can inspect the state of our application and observe the actions, and optional payloads associated with the action:\n\n![Incomplete To Dos](/img/2018/vuex-for-ngrx-devs-dispatch-actions.gif)\n\n## Conclusion\n\nCreating apps using Vue with the Vuex state management pattern + library is easy, robust and very similar to building apps with Angular and NgRx.\n","author":{"@type":"Person","image":"/headshots/brian-love-1.jpg","name":"Brian Love","sameAs":"https://brianflove.com/"},"datePublished":"2018-11-30T00:00:00.000Z","description":"Learn to build manage state in a Vue.js application using Vuex from the perspective of an Angular dev using NgRx.","headline":"Vuex for NgRx Devs","name":"Vuex for NgRx Devs","url":"https://brianflove.com"}