Store by decorators

Also supported way to configure your Store

State

First of all, let`s create a State. For example, we have some items which we want to display in a window by pages.

interface PagingModel {
    pages: number;
    currentPage: number;
    total: number;
}

type State = {
    items: Item[];
    pageSize: number;
    itemsOnDisplay: Item[];
    paging: PagingModel;
}

const InitState: State = {
    items: [],
    pageSize: 5,
    itemsOnDisplay: [],
    paging: {
        pages: 0,
        currentPage: 0,
        total: 0,
    }
}

After that we can design our Event Scheme. Let`s begin from Event Scope - which Events do we need?

enum Events {
    LoadItems = 'LoadItems',
    ItemsLoaded = 'ItemsLoaded',
    PageChanged = 'PageChanged',
}

It is not nessesary to use Enum, but it is good practice to keep strings in constants of enums to easily modify them. Now, prepare your handers.

Handlers

If we want to use decorators, for binding store and events we should create Store class that extends ProtoStore class and then, using decorators, describe its methods as handlers

To shorten our event-handlers list I use a writeAs option and pass an output-event key. It creates simple reducer under the cover and save our time.

Also, I use an order option that allows me to control by which order handlers would apply.


class Store extends ProtoStore<State> {
    constructor() {
        super(InitState, {}, DefaultStoreOptions);
    }

    @Action(Events.LoadItems, {
        writeAs: 'items',
    }, Events.ItemsLoaded)
    loadItems() {
        return new FoxEvent(Events.ItemsLoaded, someService.loadItems());
    }

    @Reducer(Events.ItemsLoaded, { order: 1 })
    setPagingModel(
        items: Item[],
        state: State,
    ): Partial<State> {
        return {
            paging: {
                pages: Math.round(items.length / state.pageSize),
                currentPage: 1,
                total: items.length,
            },
        }
    }

    @Reducer(Events.ItemsLoaded, { order: 2 })
    private setDisplayingItems(
        items: Item[],
        state: State,
    ): Partial<State> {
        return {
            itemsOnDisplay: (items || state.items).slice(
                state.paging?.currentPage * state.pageSize - 1,
                state.pageSize,
            ),
        };
    }

    @Action(Events.UpdatePage, {
        writeAs: 'paging',
    }, Events.PagingChanged)
    changePage(page: number, state: State): FoxEvent<PagingModel | string> {
        return state.paging.pages >= page
            ? new FoxEvent(Events.PagingChanged, {
                ...state.paging,
                currentPage: page,
            })
            : new FoxEvent('Error', 'Page number is not correct');
    }

    @Reducer(Events.PagingChanged)
    updateDisplayItemsOnPageChange(paging: PagingModel, state: State) {
        return this.setDisplayingItems(state.items, {
            ...state,
            paging,
        })
    }
}

So, here we have no type checking, cause decorators can not collect data about types using Reflect. But this way is more familiar for Angular-users, OOP-adepts and someone who likes big classes. Of course, we can't separate class for parts that's why you should be ready to growing up your Store-file. You can move some logic to helpers and organize code as you wish to optimise it.

Last updated