Advertisement

#18 Using Vuex Getters To Access The Securities And Categories

In this article we will focus on creating four different vuex getters to facilitate the retrival of the securites and categories from the store. The purpose of using the getters instead of just accessing what we want from the different states is in the case of both the securities and categories they have fields that we wish to have populated whenever we retrieve them. By using getters we be sure that the category and market fields will be populated instead of set to null whenever we access either all of the securities or just an individual one. The same goes for the navigation properties on our security categories class.

A new view

  • root
    • src
      • views
        • Securities.vue

Now that have all of this data in our store we need a place to view it and to that end we will start by creating a new component that we can point to from a new route (a).

Securities.vue

<template lang="pug">
div Securities
</template>
(a) The new securities view that we will use to interact with our securities data.

A new button needs a new icon

  • root
    • src
      • features
        • font-awesome.ts

With new view comes the requirement that we have a way to access it. In this case we are going to create a new button on our nav bar so we need to import a new icon for it (b).

font-awesome.ts

...
import {
    ...
    faMoneyBill,
    ...
} from "@fortawesome/free-solid-svg-icons";
...
library.add(faMoneyBill);
...
(b) Importing the new icon that we will use for our securities route.

Adding the routes enum

  • root
    • src
      • components
        • routing
          • types.ts

Next up we need to add a new entry to our routes enum (c).

types.ts

export enum Routes {
    ...
    Securities,
}
(c) Adding a value to our routes enum for the securities view.

Securities Route Entry

  • root
    • src
      • components
        • routing
          • routing-service.ts

All the pieces are now in place for use to create a new route entry for the securities view (d).

routing-service.ts

...
const securities = new RouteEntry({
    component: () => import(/* webpackChunkName: "securities" */ "../../views/Securities.vue"),
    name: "securities",
    parent: home,
    path: "/securities",
    route: Routes.Securities,
});

export class RoutingService {
    ...
    private readonly _routes = [
        ...
        securities,
    ];
    ...
}
(d) The route entry that connects the url to the new securities view.

Securities Button (template)

  • root
    • src
      • components
        • main-nav
          • TheMainNav.vue

Time to add a new button to our main nav (e).

TheMainNav.vue

<template lang="pug">
nav.main-nav
    ...
    MainNavButton(
        v-bind:icon="'money-bill'"
        v-bind:label="'Securities'"
        v-bind:route="securities")
</template>
(e) Adding the new button for the securities view to the main nav template.

Securities Button (script)

  • root
    • src
      • components
        • main-nav
          • TheMainNav.vue

To make our button work we just need to add a field to our script that is set to the securities value of the routes enum (f).

TheMainNav.vue

<script lang="ts">
...
export default class TheMainNav extends Vue {
    ...
    private readonly securities = Routes.Securities;
}
</script>
(f) Adding the field that is set to the securities value of the routes enum.

The constants for the vuex getters

  • root
    • src
      • store
        • store-constants.ts

Just like with the state the getters that will be available from our store are stored in an object and we will be accessing them through a string indexer. In order to not have to work about the exact details of the string we once again need to add a few string constants to our store (g).

store-constants.ts

...
export const GETTER_SECURITIES = "GETTER_SECURITIES";
export const GETTER_SECURITY = "GETTER_SECURITY";
export const GETTER_SECURITY_CATEGORIES = "GETTER_SECURITY_CATEGORIES";
export const GETTER_SECURITY_CATEGORY = "GETTER_SECURITY_CATEGORY";
...
(g) Adding the constants that we will be using to access the getters that will be available from our state.

Getter convenience type

  • root
    • src
      • store
        • store-types.ts

Just like with our action tree we can add a new convenience type that we can use when defining a getter tree (h).

store-types.ts

import {
    ...
    GetterTree,
    ...
} from "vuex";
...
export type StoreGetterTree = GetterTree<IStoreState, IStoreState>;
...
(h) Adding a getter tree type to our store types.

Defining the shape of our security getters

  • root
    • src
      • store
        • security-types.ts

As it stands right now we need to create four different getters for our securities. We need two getters that we can use to retrieve a single security and category as well and two more that we can use to get all securities and all categories (i). In the case of vuex getters we can specify them to either return a scalar type or a function. For the getters that return a single object the getter itself will return a function and the others will return arrays.

security-types.ts

import {
    GETTER_SECURITIES,
    GETTER_SECURITY,
    GETTER_SECURITY_CATEGORIES,
    GETTER_SECURITY_CATEGORY,
    ...
} from "@/store/store-constants";
...
export interface ISecurityGetters {
    [GETTER_SECURITIES]: SecurityModel[];
    [GETTER_SECURITY]: (id: number) => SecurityModel;
    [GETTER_SECURITY_CATEGORIES]: SecurityCategoryModel[];
    [GETTER_SECURITY_CATEGORY]: (id: number) => SecurityCategoryModel;
}
...
(i) Exporting the interface for our security getters.
Advertisement

Time to add the implementation of the getters

  • root
    • src
      • store
        • security-module.ts

The time has come for us to implement our getters. For the getters that will be returning arrays we will use the individual getters by way of a call to map to populate the arrays (j).

security-module.ts

import {
    GETTER_SECURITIES,
    GETTER_SECURITY,
    GETTER_SECURITY_CATEGORIES,
    GETTER_SECURITY_CATEGORY,
    ...
} from "@/store/store-constants";

import {
    IStoreState,
    StoreGetterTree,
} from "@/store/store-types";

import {
    ISecurityGetters,
    ...
} from "@/store/security-types";
...
export const securitiesGetters: StoreGetterTree = {
    [GETTER_SECURITIES]: (state: IStoreState, getters: ISecurityGetters) => {
        return state[STATE_SECURITIES].items.map((x) => getters[GETTER_SECURITY](x.id));
    },
    [GETTER_SECURITY]: (state: IStoreState, getters: ISecurityGetters) => {
        return (id: number) => {
            const security = state[STATE_SECURITIES].items.find((x) => x.id === id);
            if (typeof(security) === "undefined") {
                throw new Error(`Unable to find security with id: ${id}.`);
            }
            const category = getters[GETTER_SECURITY_CATEGORY](security.categoryId);
            const market = state[STATE_SECURITY_MARKETS].items.find((x) => x.id === security.marketId);
            if (typeof(market) === "undefined") {
                throw new Error(`Unable to find market with id: ${security.marketId}.`);
            }
            return security
                .setCategory(category)
                .setMarket(market);
        };
    },
    [GETTER_SECURITY_CATEGORIES]: (state: IStoreState, getters: ISecurityGetters) => {
        return state[STATE_SECURITY_CATEGORIES].items.map((x) => getters[GETTER_SECURITY_CATEGORY](x.id));
    },
    [GETTER_SECURITY_CATEGORY]: (state: IStoreState) => {
        return (id: number) => {
            const category = state[STATE_SECURITY_CATEGORIES].items.find((x) => x.id === id);
            if (typeof(category) === "undefined") {
                throw new Error(`Unable to find category with id: ${id}.`);
            }
            const segment = state[STATE_SECURITY_SEGMENTS].items.find((x) => x.id === category.segmentId);
            if (typeof(segment) === "undefined") {
                throw new Error(`Unable to find segment with id: ${category.segmentId}.`);
            }
            const territory = state[STATE_SECURITY_TERRITORIES].items.find((x) => x.id === category.territoryId);
            if (typeof(territory) === "undefined") {
                throw new Error(`Unable to find territory with id: ${category.territoryId}.`);
            }
            const type = state[STATE_SECURITY_TYPES].items.find((x) => x.id === category.typeId);
            if (typeof(type) === "undefined") {
                throw new Error(`Unable to find security type with id: ${category.typeId}.`);
            }
            return category
                .setSegment(segment)
                .setTerritory(territory)
                .setType(type);
        };
    },
};
...
(j) Adding the implementation for our getters.

An interface for future use

  • root
    • src
      • store
        • store-types.ts

In the future we will need to use getters from different 'modules' and we would like to do this in a type safe manor so it is time to specify a new interface for our store getters (k).

store-types.ts

...
import {
    ISecurityGetters,
    ISecurityState,
} from "@/store/security-types";
...
export interface IStoreGetters extends ISecurityGetters { }
...
(k) Adding a new interface for the store getters.

Adding the getters to our store

  • root
    • src
      • store
        • store.ts

It is a simple matter of using the spread operator to add our getters to our store (l).

store.ts

...
import {
    securitiesGetters,
    securitiesState,
} from "@/store/security-module";
...
const getters = {
    ...securitiesGetters,
};
...
export const store = new Vuex.Store({
    actions,
    getters,
    mutations,
    state,
});
(l) Time to add the getters to our store.

The getter decorator

  • root
    • src
      • store
        • decorators.ts

The last step that we need to make before returning to the securities view is to create a new decorator that we can use to bind our getters to a field in our views (m).

decorators.ts

import {
    ...
    mapGetters,
    ...
} from "vuex";
...
// tslint:disable:variable-name
...
export const Getter = createDecoratorFactory("computed", mapGetters);
...
// tslint:enable:variable-name
(m) Adding a getter decorator to our list of decorators.

Binding the data

  • root
    • src
      • views
        • Securities.vue

With the decorator in place we can test to make sure that everything is working correctly (n). After adding this code if we check the console we should see all of our securities and categories listed there with their navigation properties set properly and not to null.

Securities.vue

<script lang="ts">
import { Component, Vue } from "vue-property-decorator";

import {
    GETTER_SECURITIES,
    GETTER_SECURITY_CATEGORIES,
    Getter,
    SecurityCategoryModel,
    SecurityModel,
} from "@/store";

@Component
export default class Securities extends Vue {
    @Getter(GETTER_SECURITIES) private readonly securities!: SecurityModel[];
    @Getter(GETTER_SECURITY_CATEGORIES) private readonly categories!: SecurityCategoryModel[];
    
    private created() {
        console.log(this.securities);
        console.log(this.categories);
    }
}
</script>
(n) Testing to make sure that the getters are working correctly by printing the securities and categories to the console.
Exciton Interactive LLC
Advertisement