Ionic Framework with VueJS: Split-View Menu with Authentication Flow Using, Vuex & Vue Composition

Aaron K Saunders - Jun 1 '20 - - Dev Community

Remember: Ionic VueJS Support is Still in Beta

Overview

Ionic Framework with VueJS build a Split-View user interface with Side menu. The application uses the official vuejs state manager, vuex, for authentication state management in the login flow. We also use information from the store to protect routes and hide the side-menu when the user is not authenticated.

The second part of the blog post, we show how to do the same but using the new Vue Composition API to manage state and implement the same functionality.

Since Vue Composition API is for v3.x, we re using a plugin to provide that functionality to this v2.x application

This post is to cover the important pieces of the code; the bulk of the details are contained in the two video showing each of the specific implementations

📺 You can jump to end of post to see the videos...

Using Vuex

Setup

Import the store in main.js

import store from "./store";
Enter fullscreen mode Exit fullscreen mode

Checking for user when app starts up

store.dispatch("user/checkAuth").then(() => {
  new Vue({
    render: h => h(App),
    store,
    router
  }).$mount("#app");
});
Enter fullscreen mode Exit fullscreen mode

Protecting Routes

We need to get access to the state information in our beforeEnter handler to protect routes. Since we are using namespaces, the user state is at store.state.user and the actual user is store.state.user.user in this case we are checking for the existence of a user to determine if we should allow access to the specific route

const privateRoute = (to, from, next) => {
  let userStore = store.state.user;
  let isAuthenticated = userStore.user !== null;
  console.log("isAuthenticated:" + isAuthenticated);

  if (!isAuthenticated) {
    next({ name: "login" });
  } else {
    next();
  }
};
Enter fullscreen mode Exit fullscreen mode

Logging Into Application

For logging into the application we can access the store using $store and dispatch the login function the combination of the namespace and the action and passing the payload.

// login.vue
export default {
  name: "Login",
  methods: {
    async doLogin() {
      let result = await this.$store.dispatch("user/login", {
        email: this.email,
        password: this.password
      });
      if (result) {
        console.log(this.$store.state);
        this.$router.push("/");
      }
    }
  },
Enter fullscreen mode Exit fullscreen mode

Controlling Menu Display and Content

we use a computed property to get the currentUser

computed: {
  currentUser() {
    return this.$store.state.user.user;
  }
},
Enter fullscreen mode Exit fullscreen mode

For logging out we dispatch the logout action in the same manner that we dispatched the login action above

async logout() {
  let menuController = document.querySelector("#main-menu");
  await menuController.close(true);
  await store.dispatch("user/logout");
  this.$router.replace("/login");
}
Enter fullscreen mode Exit fullscreen mode

To hide the menu we use currentUser computed property from the component, we could have used isLoggedIn

<template >
  <ion-menu content-id="main" side="start" 
            id="main-menu" v-if="currentUser"
...rest of template code
</template>
Enter fullscreen mode Exit fullscreen mode

The Store

Since we we are using namespaces we need to do a bit more setup on the store.

// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import user from './auth-store'

Vue.use(Vuex)

export default new Vuex.Store({
  modules: {
    user,
  },
})
Enter fullscreen mode Exit fullscreen mode
export default {
  namespaced: true,

  state: {
    user: null,
    authChecked: false,
  },

  // ACTIONS (asynchronous)
  actions: {
    checkAuth({ commit }) {
      let state = this.state.user;
      return new Promise((resolve) => {
        commit("checkAuth", { authChecked: true, user: state.user });
        resolve(true);
      });
    },
    login({ commit }, payload) {
      if (payload.email !== "") {
        commit("hasUser", { ...payload });
        return true;
      } else {
        commit("clearUser", {});
        return false;
      }
    },
    logout({ commit }) {
      return new Promise((resolve) => {
        commit("clearUser", {});
        resolve(true);
      });
    },
  },

  // MUTATIONS ( set the state )
  mutations: {
    hasUser(state, payload) {
      state.user = { ...payload };
    },
    clearUser(state, payload) {
      state.user = null;
    },
    checkAuth(state, payload) {
      state.user = payload.user;
      state.authChecked = payload.authChecked;
    },
  },
};
Enter fullscreen mode Exit fullscreen mode

Using Vue Composition

Setup

// main.js
import VueCompositionApi from "@vue/composition-api";
Vue.use(VueCompositionApi);
Enter fullscreen mode Exit fullscreen mode

Protecting Routes

We need to get access to the state information in our beforeEnter handler to protect routes

const privateRoute = (to, from, next) => {

let { state } = useAuth();
let isAuthenticated = state.value.loggedIn;
... rest of the code
Enter fullscreen mode Exit fullscreen mode

Logging Into Application

For logging into the application we don't need to use the setup approach as we did above, you can just import useAuth and call the function on the module

<script>
import useAuth from "../useAuth";
export default {
  name: "Login",
  methods: {
    async doLogin() {
      let { login } = useAuth();
      login();
      this.$router.push("/");
    }
  },
... rest of script
</script>
Enter fullscreen mode Exit fullscreen mode

Controlling Menu Display and Content

<script>
import useAuth from "../useAuth";

export default {
  name: "Menu",
  // VUE COMPOSITION
  setup() {
    let { state, logout } = useAuth();
    return {
      state: state.value,
      logout
    };
  },
Enter fullscreen mode Exit fullscreen mode

In this component, we are using the new setup functionality to incorporate the information returned from the vue composition api in the the component as data properties.

Now to call the logout function you must use this.logout. To hide the menu we can get the loggedIn state from the component now

<template >
  <ion-menu content-id="main" side="start" 
            id="main-menu" v-if="state.loggedIn">
...rest of template code
</template>
Enter fullscreen mode Exit fullscreen mode

The Store

I tried to keep the store straight forward with no real code for authentication, this is really to demonstrate the approach.

So just calling the login function will log the user in and set the appropriate state values.

The logout function clears the user object and sets loggedIn to false.

// useAuth.js
import Vue from "vue";
import VueCompositionApi, { computed, ref } from "@vue/composition-api";
Vue.use(VueCompositionApi);

// STATE
const state = ref({
  user: {},
  loggedIn: false,
  error: {},
});

export default function() {
  return {
    state: computed(() => state.value),
    login: () => {
      state.value.user = { id: 100, name: "aaron" };
      state.value.loggedIn = true;
    },
    logout: () => {
      state.value.user = {};
      state.value.loggedIn = false;
    },
  };
}
Enter fullscreen mode Exit fullscreen mode

📺 Videos


. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .