2
votes

I have the following Vuex module using Typescript that I'm exporting from a library:

import * as types from '@/store/types';
import {Formio} from 'formiojs';
import { VuexModule, Module, Mutation, Action } from 'vuex-module-decorators'

interface RoleItem {
  _id: string;
  title: String;
  admin: Boolean;
  default: Boolean;
}

interface RoleList {
  [key: string]: RoleItem;
}

export class Auth extends VuexModule {
  public user: {}
  public loggedIn: boolean
  public roles: {}
  public forms: {}
  public userRoles: {}

  @Action
  setUser({ state, commit, dispatch }, user) {
    commit(types.SET_USER, user);
    dispatch('setLoggedIn', true);
    dispatch('setUserRoles', state.roles);
  }
  @Action
  setLoggedIn({commit}, loggedIn) {
    commit(types.SET_LOGGED_IN, loggedIn);
  }
  @Action
  getAccess({ commit, dispatch, getters }) {
    const projectUrl = Formio.getProjectUrl();
    Formio.request(projectUrl + '/access')
      .then(function(accessItems) {
        commit(types.SET_ROLES, accessItems.roles);
        commit(types.SET_FORMS, accessItems.forms);
        if (getters.getLoggedIn) {
          dispatch('setUserRoles', accessItems.roles);
        }
    });
  }
  @Action
  setUserRoles({ commit, getters }, roles: RoleList) {
    const roleEntries = Object.entries(roles);
    const userRoles = getters.getUser.roles;
    const newRolesObj = {};
    roleEntries.forEach((role) => {
      const roleData = role[1];
      const key = 'is' + role[1].title.replace(/\s/g, '');
      newRolesObj[key] = !!userRoles.some(ur => roleData._id === ur);
    });
    commit(types.SET_USER_ROLES, newRolesObj);
  }

  @Mutation
  [types.SET_USER](user) {
    this.user = user;
  }
  @Mutation
  [types.SET_LOGGED_IN](loggedIn: boolean) {
    this.loggedIn = loggedIn;
  }
  @Mutation
  [types.SET_ROLES](roles: RoleList) {
    this.roles = roles;
  }
  @Mutation
  [types.SET_FORMS](forms) {
    this.forms = forms;
  }
  @Mutation
  [types.SET_USER_ROLES](userRoles) {
    this.userRoles = userRoles;
  }
}

export default Auth;

I want to simply import it it in the parent vue app as a namespaced Vuex module and add it to my store as a new module:

import Vue from 'vue';
import Vuex from 'vuex';
import { Auth } from 'vue-formio'

Vue.use(Vuex);

...

resourceModules.auth = Auth;

export default new Vuex.Store({
  modules: resourceModules,
  strict: debug,
});

That part all works fine. The problem is setting the namespaced: true and name :auth properties in the exported store. From what I've read, I should be able to do it with the @Module decorator like this:

@Module({ namespaced: true, name: 'auth' })
export class Auth extends VuexModule {

However, as soon as I add the parentheses after the @Module decorator, I get this TS error in my IDE:

TS1238: Unable to resolve signature of class decorator when called as an expression. Cannot invoke an expression whose type lacks a call signature. Type 'void' has no compatible call signatures.

As seen in the vuex-module-decorators code, these are allowed options:

export interface StaticModuleOptions {
  /**
   * name of module, if being namespaced
   */
  name?: string;
  /**
   * whether or not the module is namespaced
   */
  namespaced?: boolean;
  /**
   * Whether to generate a plain state object, or a state factory for the module
   */
  stateFactory?: boolean;
}

This is my first foray into Typescript, so I'm stumped. I'm still researching, but in the meantime, how do I add the namespacing to this Vuex module with Typescript?

2

2 Answers

0
votes

A quickfix I figured out is to add

import { ModuleOptions } from 'vuex-module-decorators/dist/types/moduleoptions';

@Module({ dynamic: true, name: 'user', namespaced: true, store: Store } as ModuleOptions)

to the configuration object.

0
votes

The error is because you're calling @Actions with params it doesn't understand. Docs here.

Probably the most confusing part of using vuex-module-operators is the fact that, unlike in normal Vuex, the first param (the ActionContext) of @Actions is skipped. It is available as this.context.

Here's how one of your actions would work with this package:

@/store/auth.ts

import { ActionContext } from 'vuex';
import { Module, VuexModule, Action } from 'vuex-module-decorators';
import * as types from './types';
import store from '.';

@Module({ namespaced: true, store, name: 'auth' })
export default class AuthStore extends VuexModule {

  @Action
  async setUser(user) {
    const { state, commit, dispatch } = this.context;
    commit(types.SET_USER, user);
    dispatch('setLoggedIn', true);
    dispatch('setUserRoles', state.roles); // could be `this.roles`
  }
}

Make sure you also call getModule(Auth, store) in your main store file, after creating the store. I believe this call has no other effect than inferring types. (the store seems to work fine without it, but Typescript no longer works). Example:

@/store/index.ts

import Vue from 'vue';
import Vuex from 'vuex';
import Auth from './auth';
import { getModule } from 'vuex-module-decorators';

Vue.use(Vuex);

const store = new Vuex.Store({
  //...
  modules: {
    auth: Auth
  }
});

getModule(Auth, store);

export default store;

As a side note, I don't fully understand why you're dispatching setUserRoles with state.roles - state is available in setUserRoles action just like it's available in setUser action, so no need to send the roles as param.
Did you mean dispatch('setUserRoles', user.roles);?