championswimmer / vuex-module-decorators Goto Github PK
View Code? Open in Web Editor NEWTypeScript/ES7 Decorators to create Vuex modules declaratively
Home Page: https://championswimmer.in/vuex-module-decorators/
License: MIT License
TypeScript/ES7 Decorators to create Vuex modules declaratively
Home Page: https://championswimmer.in/vuex-module-decorators/
License: MIT License
Hi @championswimmer 😃
I'm putting logger and api client instances in my global state because I want to use them in almost all my store modules.
To avoid using each time this.rootState.logger.debug('blabla')
I'm trying to make all my modules herited from a EnrichedVuexModule
as follow:
export class EnrichedVuexModule extends VuexModule {
public test(): string {
return "heyyyy";
}
get $api() {
return store.state.apiClient;
}
get $log() {
return store.state.logger;
}
};
And my modules are defined with the following:
@Module({ dynamic: true, store, name: 'authentication' })
export class Authentication extends EnrichedVuexModule {
My TypeScript "linter" detects that this.$api
and this.$log
exist due to the inheritance but at runtime this.$api
and this.$log
are not defined (while I can reach them through this.rootState.XXX
). Same for the test()
function.
Since I'm using dynamic modules and that some stuff is made with decorators by your package, do you know what could cause this trouble please?
Thank you,
As it stands right now the only way to access a getter within another is to use this.context.getters['getterName']. Are there any plans to allow this.getterName from within another getter? Right now it returns undefined.
Is it possible to split a module into multiple files?
It is common to write a Vuex module with 5 files where you have module.state.ts
, module.getters.ts
, module.mutations.ts
, module.actions.ts
and module.ts
(which assembles the 4 files into the same module).
I'd like to report a bug
Given the following code:
@Module({ dynamic: true, store, name: 'user' })
class User extends VuexModule implements IUserState {
token = 'something';
@MutationAction({ mutate: [ 'roles', 'name', 'avatar' ] })
async GetInfo(input: string) {
log.debug(this.token)
log.debug(input)
...
}
When executing this MutationAction from outside the module, the logging of this.token
returns undefined
. However, If I pass the value from this module from outside this module as the input
parameter, then I can get the correct value:
UserModule.GetInfo(UserModule.token).then(() => { ... }
So UserModule.token
does provide the correct value, only you need to re-enter it in the module... which does not make much sense, but it's a workaround anyway.
I also observed that while debugging, the token
value inside the Module can be accessed by using this.state.token
instead of this.token
. The only problem with this, is that typescript does not accept this as valid typings...
I don't know if this has something to do with dynamic modules or a more generic bug. Just leaving this here.
that's look a little wired .
cant‘t get the variable as state
Is it possible to do the .prototype
part internally on a module in getModule
?
When doing getModule(AdminLayoutStoreModule.prototype, this.$store);
my ide throws this error:
Argument type AdminLayoutStoreModule is not assignable to parameter type ConstructorOf<AdminLayoutStoreModule>.
Without the proto it is fine.
http://json.schemastore.org/tsconfig
"noEmitHelpers": {
"description": "Do not generate custom helper functions like __extends in compiled output.",
"type": "boolean"
},
'emitHelpers' is enabled by default.
so that we can do this.x = newXvalue inside actions as well ;)
when we call getModule() with the same object again, return the same accessor, instead of generating again
Like this
computed: {
...mapState({
a: state => state.some.nested.module.a,
b: state => state.some.nested.module.b
})
},
methods: {
...mapActions([
'some/nested/module/foo',
'some/nested/module/bar'
])
}
The following login action will produce "ERR_ACTION_ACCESS_UNDEFINED" exception (see callstack below). The original rejection reason is lost.
@Module({name: 'auth', namespaced: true})
export default class AuthModule extends VuexModule {
@Action
async login(payload: any): Promise<any> {
return Promise.reject('login failed');
}
}
ERR_ACTION_ACCESS_UNDEFINED: Are you trying to access this.someMutation() or this.someGetter inside an @action?
That works only in dynamic modules.
If not dynamic use this.context.commit("mutationName", payload) and this.context.getters["getterName"]
Error: Could not perform action login
at Store.eval (webpack-internal:///./node_modules/vuex-module-decorators/dist/esm/index.js:311:33)
at step (webpack-internal:///./node_modules/vuex-module-decorators/dist/esm/index.js:96:23)
at Object.eval [as throw] (webpack-internal:///./node_modules/vuex-module-decorators/dist/esm/index.js:77:53)
at rejected (webpack-internal:///./node_modules/vuex-module-decorators/dist/esm/index.js:68:65)
undefined
Is there any way to combine dynamic modules with your other awesome project?
I have them working together just fine registering the modules manually.
I am using your class with a IOC (inversify) having injection issues.
I cant seem to grasp how to call your constructor , which I would have to inject during the injection process.
I have this as starting point.
`
new VuexModule(<ModuleOptions>{})
syntax*/
modules?: ModuleTree
namespaced?: boolean
getters?: GetterTree<S, R>
state?: S | (() => S)
mutations?: MutationTree
actions?: ActionTree<S, R>
`
`
decorate(injectable(), VuexModule);
decorate(unmanaged(), VuexModule, 1);
@module
@Injectable()
export class ServiceUserData extends VuexModule {
constructor(@inject('moduleOptions') moduleOptions: any) )
{
super(moduleOptions)
}`
and then bind it in the DI container.
.....bind('Dependency').to(Dependency)
….bind('ModuleOptions').toConstantValue({});
cheers!!
Since state values might be initialized to undefined on purpose, excluding undefined here would be problmatic.
Action decorator doesn't return any value bug fixed by #56 PR.
But it's not created 0.9.1 tag, just updated package.json only.
Then, i can't use bug fixed version.
Please create new 0.9.1 tag ASAP.
Thanks.
When using a plugin that stores in local storage, it looks like the class instantiation overwrites the stored values on page load.
Do you have an example that keeps those values after page reload etc.?
What license is this package released under?
using the exact example of the docs i'm getting a:
@mutation
increment({delta: number}) {
this.count+=delta
}
Argument of type 'TypedPropertyDescriptor<({ delta: number }: { delta: any; }) => void>' is not assignable to parameter of type 'TypedPropertyDescriptor'.
Types of property 'set' are incompatible.
Type '((value: ({ delta: number }: { delta: any; }) => void) => void) | undefined' is not assignable to type '((value: Function) => void) | undefined'.
Type '(value: ({ delta: number }: { delta: any; }) => void) => void' is not assignable to type '((value: Function) => void) | undefined'.
Type '(value: ({ delta: number }: { delta: any; }) => void) => void' is not assignable to type '(value: Function) => void'.
Types of parameters 'value' and 'value' are incompatible.
Type 'Function' is not assignable to type '({ delta: number }: { delta: any; }) => void'.
({ delta: number }: { delta: any; }) => void is not a Function?
Hey, not sure what's going on here. I'm attempting to port a small codebase from plain JS to TypeScript, and thought I'd use this module for a typesafe store. I have it running fine in a more minimal app, so I know that it works and that I'm generally using it correctly.
Unfortunately, I get this error in my console whenever I try loading a module written using this decorator:
TypeError: class constructors must be invoked with |new| app.js:65377:31
My store has 3 submodules. This happens when I convert any one of them to use this decorator. It even happens if I convert one but clear its body so it is essentially an empty module.
I've put together an example. Sorry it isn't more minimal, but again, a minimal example doesn't seem to replicate this:
https://github.com/ndarilek/vuex-module-decorators-issue
Standard vue-cli app, so yarn install && yarn serve
. Can probably safely ignore the lambda error, as it happens even when everything works. To swap out TS and JS implementations, cp src/store.old/player.js src/store/player.js
and rm src/store/player.ts
. With the JS module in place, things seem to work. With the TS module, I get the above error.
Thanks for any help. Apologies if I've made a silly mistake--I'm learning TypeScript so I may be using decorators or its idioms incorrectly.
I've tried creating a module like so:
@Module
export default class Main extends VuexModule {
namespaced = true;
@MutationAction({mutate: ['var']})
async fetchData() {
// Do something
}
}
and creating a Vuex Store like so:
export default new Vuex.Store({
modules: {
main: Main
}
});
However when I try and call this action from a component (this.$store.dispatch("main/fetchData")
), I get an error stating that no such action exists. If I call the action without the namespace (this.$store.dispatch("fetchData")
) it works.
Am I creating the namespaced module incorrectly?
Hi,
I would like to commit just when I want inside an action, like:
@Action({})
public customAction(credentials: Credentials) {
return new Promise((resolve, reject) => {
if (true) {
this.commit('setUser');
resolve("a string");
} else {
this.commit('deleteUser',);
reject();
}
});
}
But the decorator is always complaining about parameters I passed to it. It requires me to set a "commit" property but I don't want to. I could set an empty string @Action({ commit: '' })
but I don't know if it's the right way to do.
By the way, I tried to handle the then/catch when calling this.$store.dispatch('customAction').then(...
but I can't get the value of the called resolve(...). Do you know what could be the problem?
Thank you,
Hi,
Following code does not work:
@Action
changeCurrentDistrict(district: number) {
if (!this.doesDistrictExist(district)) {
return false;
}
this.setCurrentDistrict(district);
return true;
}
@Action
previousDistrict() {
return this.changeCurrentDistrict(this.currentDistrict - 1);
}
@Action
nextDistrict() {
return this.changeCurrentDistrict(this.currentDistrict + 1);
}
Error:
Unhandled promise rejection Error: "ERR_ACTION_ACCESS_UNDEFINED: Are you trying to access this.someMutation() or this.someGetter inside an @Action?
That works only in dynamic modules.
If not dynamic use this.context.commit("mutationName", payload) and this.context.getters["getterName"]
How can I fix that?
Thanks
In the official Vuex-documentation it is stated, that multiple commits in a single action are possible. For example here:
actions: {
checkout ({ commit, state }, products) {
// save the items currently in the cart
const savedCartItems = [...state.cart.added]
// send out checkout request, and optimistically
// clear the cart
commit(types.CHECKOUT_REQUEST)
// the shop API accepts a success callback and a failure callback
shop.buyProducts(
products,
// handle success
() => commit(types.CHECKOUT_SUCCESS),
// handle failure
() => commit(types.CHECKOUT_FAILURE, savedCartItems)
)
}
}
Now when I tried to get a similar behavior with this neat module, only the first commit/dispatch gets called and the action ends afterwards.
Here is an example:
@Mutation
public [PURGE_AUTH]() {
this.token = '';
this.user = {id: '', username: ''};
localStorage.removeItem('token');
delete axios.defaults.headers.common['X-AUTH-TOKEN'];
}
@Action
public async [LOGOUT]() {
await doLogout();
console.log('COMMITTING');
this.context.commit(PURGE_AUTH);
console.log('DISPATCHING');
this.context.dispatch(RESET_ALL);
console.log('AFTER DISPATCH');
return;
}
Only 'COMMITTING' will be printed to the console and the the other dispatches/commits do not get called. I have also tried to use the await
keyword for the commits but that does not do anything.
for example, id will always be null
@Module
class MyModule extends VuexModule {
id: number| null = null
@MutationAction({ mutate: ['id'] })
async updateId(id: number | null) {
return { id }
}
}
but it will work if I initial id with number
Affects version: "vuex-module-decorators": "0.8.0"
Plain old store works well:
export default {
namespaced: true,
actions: {
value () {
return 'Test1'
},
promise () {
return Promise.resolve('Test1')
}
}
}
Decorated store doesn't:
import { Action, Module, VuexModule } from 'vuex-module-decorators'
@Module({namespaced: true})
export class Test2 extends VuexModule {
@Action
public value () {
return 'Test2'
}
@Action
public promise () {
return Promise.resolve('Test2')
}
}
Call:
console.log(await this.$store.dispatch('test1/value'))
console.log(await this.$store.dispatch('test1/promise'))
console.log(await this.$store.dispatch('test2/value'))
console.log(await this.$store.dispatch('test2/promise'))
Result:
Test1
Test1
undefined
undefined
While debugging I found out, that the inner promise gets indeed resolved but for unknown reason not propagated to the action promise.
Hi, I've been trying for hours to add this to a brand new Vue Cli 3 project with TS and I cannot get it to work. Does anyone have a simple fully working counter module ?
No matter what I do, I get a console error:
Uncaught TypeError: Class constructor VuexModule cannot be invoked without 'new'
How can I create stores at runtime on demand? I have some pages that need to have their own specific namespaced modules
Not sure what I am doing wrong. I've updated to the latest version 0.7.1 and I still cant get non dynamic modules to work. I'm using your examples and I'm getting an error of Property 'count' does not exist on type 'CounterModule'. Also the same error for the incr and decr method calls.
I have created an exact example here https://github.com/jculverwell/vuemoduledecoratorssample
My code is as follows:
import Vue from 'vue';
import Vuex from 'vuex';
import CounterModule from '@/store/Counter2';
Vue.use(Vuex);
export default new Vuex.Store({
state: { version: 1 },
modules: {
CounterModule,
},
mutations: {},
actions: {},
});
import {Module, VuexModule, Mutation, Action, getModule} from 'vuex-module-decorators';
@Module
export default class CounterModule extends VuexModule {
count = 0;
@Mutation increment(delta: number) {this.count += delta; }
@Mutation decrement(delta: number) {this.count -= delta; }
// action 'incr' commits mutation 'increment' when done with return value as payload
@Action({commit: 'increment'}) incr() {return 5; }
// action 'decr' commits mutation 'decrement' when done with return value as payload
@Action({commit: 'decrement'}) decr() {return 5; }
}
<template>
<div class="home">
<h1>Version:{{this.$store.state.version}}</h1>
<h1>Count: {{count}}</h1>
<button @click="increment">+</button>
<button @click="decrement">-</button>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import store from '@/store/store';
import {getModule} from 'vuex-module-decorators'
import CounterModule from '@/store/Counter2'
@Component
export default class Home extends Vue {
get count() {
return CounterModule.count
}
increment(){
CounterModule.incr();
}
decrement(){
CounterModule.decr();
}
}
</script>
Hi @championswimmer 😃
I was wondering if there is a way with your plugin to define a method with parameters as a getter?
get getUser() { ... }
works
get getUser(id: string) { ... }
does not work since the ES6 syntax does not allow get methods to have parameters
I didn't find anything about that in your code but I was thinking about something like:
@Getter
getUser(id: string) { ... }
Or maybe I missed something?
Thank you,
I've been experimenting with this library to improve TypeScript interaction with Vuex in my projects but I've hit a problem (or perhaps something that I misunderstood from the docs):
When I call actions via getModule(...) accessor on the first argument to the action seems to work correctly. The second argument however is always undefined inside the action.
The following example (and comments) show the problem:
Is this a bug or can actions only have a single argument (= the payload similar to vanilla vuex)?
// file SomeModule.ts
import store from '@/setup/store';
import {Action, Module, Mutation, VuexModule} from 'vuex-module-decorators';
@Module({dynamic: true, store, name: 'someModule'})
export default class SomeModule extends VuexModule {
@Action
public async doSomething(arg1: string, arg2: string) {
console.log(arg1); // has correct value of first argument
console.log(arg2); // is always undefined
}
}
// file test.ts
import {getModule} from 'vuex-module-decorators';
import SomeModule from './SomeModule';
const someModule = getModule(SomeModule.prototype);
userModule.doSomething("val1", "val2");
// results in 2 log rows, first is "val1", second is "undefined"
const store = new Vuex.Store({
modules: {
appone: AppStoreModule,
apptwo: AppStoreModule
}
});
const appOneModule = getModule(AppStoreModule, store);
const appTwoModule = getModule(AppStoreModule, store);
How would this work? I'm guessing we need to specify the name in getModule
somehow?
I am trying to inject my class which extends VuexModule using a DI container based on inversifyjs.
I Would have to pass the VuexModule constructor to the DI container then inject it when the class is instantiated to be passed to super().
It would be helpful if you could provide an example of :
/*
new VuexModule(<ModuleOptions>{})
syntax*/
cheers.
"Class constructor VuexModule cannot be invoked without 'new'"
The problem exists in 0.5.0 release, with 0.4.3 everything is ok
HI
I am a bit confused about this line of code.
class Counter extends VuexModule {
counter = undefined; // it will lost in the state
}
Is it necessary to check if the state is undefined?
BTW: My English sucks...forgive me
const mutation = function (state, payload) {
for (let stateItem of params.mutate) {
if (state[stateItem] != null && payload[stateItem] != null) {
state[stateItem] = payload[stateItem];
}
else {
throw new Error(`ERR_MUTATE_PARAMS_NOT_IN_PAYLOAD
In @MutationAction, mutate: ['a', 'b', ...] array keys must
match with return type = {a: {}, b: {}, ...} and must
also be in state.`);
}
}
};
This check state[stateItem] != null && payload[stateItem] != null
is unreasonable.
em......, such as, I can't set some state to null.
May be can use 'Object.hasOwnProperty'
params.mutate.forEach(stateItem => {
if (payload.hasOwnProperty(stateItem) && state.hasOwnProperty(stateItem)) {
state[stateItem] = payload[stateItem]
}
})
Thanks for a great project. I have created a sample app using vue cli 3 and some code from your examples. I'm not sure what I am doing wrong with the getModule function but counterModule is undefined. The code is below along with a full sample here https://github.com/jculverwell/vuemoduledecoratorssample
Welcome your thoughts.
<div class="home">
<h1>Version:{{this.$store.state.version}}</h1>
<h1>Count: {{this.$store.state.counter.count}}</h1>
<button @click="increment">+</button>
<button @click="decrement">-</button>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import store from '@/store/store';
import {getModule} from 'vuex-module-decorators'
import Counter2 from '@/store/Counter2'
const counterModule = getModule(Counter2.prototype)
@Component
export default class Home extends Vue {
increment(){
counterModule.incr();
}
decrement(){
counterModule.decr();
}
}
</script>
Could you please provide full working exapmle?
First of all, I love your library, great work!
Secondly I wanted to try out the MutationAction annotation
I've tried using this test as an example, but it didn't seem to work.
@MutationAction({ mutate: ['count'] })
async updateCount(newcount: number) {
return { count: newcount };
}
It throws the following exception (from TypeScript)
Argument of type 'TypedPropertyDescriptor<(newcount: number) => Promise<{ count: number; }>>' is not assignable to parameter of type 'TypedPropertyDescriptor<(...args: any[]) => {}>'.
Types of property 'set' are incompatible.
Type '((value: (newcount: number) => Promise<{ count: number; }>) => void) | undefined' is not assignable to type '((value: (...args: any[]) => {}) => void) | undefined'.
Type '(value: (newcount: number) => Promise<{ count: number; }>) => void' is not assignable to type '(value: (...args: any[]) => {}) => void'.
Types of parameters 'value' and 'value' are incompatible.
Type '{}' is not assignable to type 'Promise<{ count: number; }>'.
Through trail and error I did get it working by typing the annotation like
@MutationAction<MyVuexModule, Promise<{ count: number }>>({ mutate: ['count'] })
async updateCount(newcount: number) {
return { count: newcount };
}
It seems by manually setting the Target Type and Response type it seems to play nice, but since this wasn't mentioned in the documentation, I would assume that there might be something wrong with my setup.
"typescript": "^2.9.2",
"vue": "^2.5.16",
"vue-class-component": "^6.2.0",
"vue-inversify-decorator": "^0.1.0",
"vue-property-decorator": "^7.0.0",
"vue-router": "^3.0.1",
"vuetify": "^1.1.12",
"vuex": "^3.0.1",
"vuex-class": "^0.3.1",
"vuex-module-decorators": "^0.3.1",
"vuex-router-sync": "^5.0.0"
and my class file
@Module({ namespaced: true })
export class MyVuexModule extends VuexModule {
count = 0;
@MutationAction<VacationCalendarVuexModule, Promise<{ count: number }>>({ mutate: ['count'] })
async updateCount(newcount: number) {
return { count: newcount };
}
}
export const instance = namespace(VacationCalendarVuexModule.name);
Thanks in advance!
I see that in here:
vuex-module-decorators/src/mutationaction.ts
Lines 21 to 32 in 615cfd8
vuex-module-decorators/src/action.ts
Lines 21 to 51 in 8b9e458
Say I have 2 modules A and B. How can I call an action of module B from an action of module A?
It would be easy if I can access context
though.
Hi,
I have a module with the following code:
import axios, { AxiosInstance } from "axios";
import { Module, VuexModule, Action, MutationAction, Mutation } from 'vuex-module-decorators'
@Module({ name: 'login', namespaced: true })
export default class LoginModule extends VuexModule {
loginResult: any;
backends: Array<string> = [
'https://192.1686.178.6:8000/',
'https://localhost:8000/',
'https://192.168.1.105:8000/',
'https://192.1686.1.12:8000/'
]
@Action
async backendLogin() {
let api: AxiosInstance = axios.create({
baseURL: this.backends[0]
});
let response;
try {
response = await api.post("/app/register/", {});
} catch (ex) {
console.log(`Error on login: ${ex}`);
}
return response;
}
}
When I try to run this code I get this.backends is undefined
, in fact all classproperties are undefined there. Am I missing something?
closes #15
So one way of doing it is
@Module
class InnerModule extends VuexModule {
}
@Module
class OuterModule extends VuexModule {
@Nested
innerModule = new InnerModule()
}
But I am exploring other options too
so this.commit this.dispatch is illegal;
and dispatch can’t get resolve payload
Since state[stateItem]
could be undefined
(e.g. state[stateItem] is initialized with undefined, and it's legal), the equality check with null should be using !==
.
Or ensuring state[stateItem]
's existence can be achieved by using state.hasOwnProperty
.
Null check on payload[stateItem]
is same since we might want to set some state value to 'undefined'
Many project is generated by vue-cli 3. It include @vue/babel-preset-app package then already have @babel/plugin-proposal-decorators.
May be need change the README.md to remind this point.
😄
Hello,
I am using your lib (0.8.0-2) and love it, but i am getting the following error when trying to use actions and it's confusing:
"Error: ERR_ACTION_ACCESS_UNDEFINED: Are you trying to access this.someMutation() or this.someGetter inside an @action? "
I don't know what i am doing wrong, so some advice would be much appreciated.
My store and component where it is used are below:
import {VuexModule, Module, Mutation, Action} from 'vuex-module-decorators'
@Module({name: "ModuleStore"})
export default class ModuleStore extends VuexModule {
posts: string[] = ['poc 1', 'poc 2'] // initialise empty for now
get totalComments (): number {
return this.posts.length;
}
@Mutation
updatePosts(posts: string[]) {
this.posts = posts
}
@Action({commit: 'updatePosts'})
async fetchPosts() {
console.log(this.context);
setTimeout(() => {
return ['prvi 2', 'drugi'];
}, 500);
}
}
import { Component, Prop, Vue } from 'vue-property-decorator';
import { TestClass } from 'models/TestClass';
import { Todo } from 'interfaces/Store';
import ModuleStore from 'store/ModuleStore';
import {getModule} from 'vuex-module-decorators';
@Component
export default class TestComp extends Vue {
@Prop()
private PropData!: TestClass;
postsModule : ModuleStore = getModule(ModuleStore, this.$store);
message: string = "Comp message";
constructor() {
super();
}
get posts(): string[] {
return this.postsModule.posts;
}
set posts(newStrings: string[]) {
this.postsModule.updatePosts(['iz kdoa 1', 'iz koda 2']);
}
async fetchPosts() {
await this.postsModule.fetchPosts();
}
}
Note: I trigger the fetchPosts on a button click and then get the error i described above!
Right now using this.context.rootState
in a getter will result in TypeError: Cannot read property 'rootState' of undefined.
How feasible is it to get context available in getters?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.