championswimmer / vuex-module-decorators

TypeScript/ES7 Decorators to create Vuex modules declaratively
https://championswimmer.in/vuex-module-decorators/
MIT License
1.8k stars 170 forks source link

Reusable getters #203

Open dan-an opened 4 years ago

dan-an commented 4 years ago

Is it possible to create dynamic modules extended from some parent module? I tried it that way:

My parent-module.ts:

import {Action, getModule, Module, Mutation, VuexModule} from 'vuex-module-decorators';

export class ParentStore extends VuexModule {
    public get getterForInherit(): any {
        return someData
    }
}

Child modules: child-one.ts:

import {Action, getModule, Module, Mutation, VuexModule} from 'vuex-module-decorators'
import {ParentModule} from './parent-module';

@Module({dynamic: true, store: Store, name: 'childOne', namespaced: true})
class FirstChildModule extends ParentModule {
    public get SecondChildGetter(): number {
        return 1;
    }
}

export const FirstChildStore: ParentModule = getModule(FirstChildModule)

child-two.ts:

import {Action, getModule, Module, Mutation, VuexModule} from 'vuex-module-decorators'
import {ParentModule} from './parent-module';

@Module({dynamic: true, store: Store, name: 'childTwo', namespaced: true})
class SecondChildModule extends ParentModule {
    public get FirstChildGetter(): number {
        return 2;
    }
}

export const SecondChildStore: ParentModule = getModule(SecondChildModule)

But when I import those modules to components getterForInherit is not available. Is it possible to do it this way?

rendrom commented 4 years ago

I created classes for inheritance in the project https://github.com/nextgis/nextgisweb_frontend/blob/master/packages/vuex-ngw/src/store/ResourceStore/ResourceStore.ts#L11

and then

@Module({ dynamic: true, store, name: 'annex' })
export class AnnexStore extends ResourceStore<AnnexProperties, MultiPolygon> {
  keynames: { [key in KeyName]: string } = {};
}

But to work, need to use tricks:

(This stop working in new version 0.11.x)

Exeteres commented 4 years ago

@dan-an @rendrom This workaround may interest you.

import { getModule, VuexModule, Module } from "vuex-module-decorators";

class CollectionBase<T> extends VuexModule {
    items: T[] = [];
    get last() {
        return this.items[this.items.length - 1];
    }
}

@Module({ name: "numbers", namespaced: true })
class NumberCollection extends CollectionBase<number> {
    items = [0, 5, 2];
}

// Get wrapped parent module to access getters
const base: any = Module({})(CollectionBase);
// Copy parent getters to child
Object.assign(NumberCollection.getters, base.getters);
// Manually register module
store.registerModule("numbers", NumberCollection);

const numbers = getModule(NumberCollection, store);
console.log(numbers.last); // 2
FullPint commented 4 years ago

@Exeteres

I did something similar, but assigned the base getters before exporting my child class. Did you attempt something like this? And if so, did you run into any problems? It looks like maybe the constructor is just not instantiating the getters?

I'd be willing to help on a fleshed out solution.

Exeteres commented 4 years ago

@FullPint

Did you attempt something like this?

Actually no. The solution I presented earlier is the last thing I tried to do in my old vue project.

It looks like maybe the constructor is just not instantiating the getters?

According to this line, the constructor does not initialize anything. The @Module decorator collects getters. If we don't apply the decorator to the base class, it will not extract getters. But it doesn’t matter, because they will still be overridden by the child class decorator. So in my solution i use Object.assign to mix base and child getters after child decorator execution.

P. S. Now I managed to create a fixed version of the decorator that copies getters.

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

function FixedModule(options: ModuleOptions) {
    return (target: any) => {
        const mod = Module(options)(target) as typeof VuexModule;
        const base = Module({})(Object.getPrototypeOf(mod));
        Object.assign(mod.getters, base.getters);
        return mod as any;
    };
}

class CollectionBase<T> extends VuexModule {
    items: T[] = [];
    get last() {
        return this.items[this.items.length - 1];
    }
}

@FixedModule({ name: "numbers", namespaced: true })
class NumberCollection extends CollectionBase<number> {
    items = [0, 5, 2];
}

store.registerModule("numbers", NumberCollection);

const numbers = getModule(NumberCollection, store);
console.log(numbers.last); // 2

But I still think this is a terrible workaround, and at the moment I'm looking for an alternative to this module.

FullPint commented 4 years ago

@Exeteres did you end up having an issue with modules colliding?

Exeteres commented 4 years ago

@FullPint I had no such issues. By the way, I have not worked with Vue since my last answer.

FullPint commented 4 years ago

@Exeteres thanks for the quick reply -- I might just have an odd typo somewhere.

seflue commented 4 years ago

What is the status on this?

FullPint commented 4 years ago

@seflue my team ended up using a different package because of the amount of generics we use.

fen89 commented 4 years ago

@FullPint can you tell which package you are now using instead?

FullPint commented 4 years ago

@fen89 we use this: https://github.com/gertqin/vuex-class-modules

But it looks like they may have merged a fix with this package. Haven’t tested it yet.

BonBonSlick commented 2 years ago

@FullPint yeah, vuex-class-modules does the trick