#18 Using Vuex Getters To Access The Securities And Categories
Saturday, October 5, 2019
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.
Parts
- Part 45: Adjusting Shares
- Part 44: Plan Percentages
- Part 43: Home Securities
- Part 42: Updating Plans
- Part 41: Plan Details View
- Part 40: Portfolio Getters
- Part 39: Portfolio Plan
- Part 38: Portfolio Category
- Part 37: Account Securities
- Part 36: Account Transfer
- Part 35: View Account Security
- Part 34: Updating Deposit
- Part 33: View Account Deposit
- Part 32: Display Account Details
- Part 31: Account Getters
- Part 30: Deposits And Securities
- Part 29: Add Accounts Details
- Part 28: Refactoring Accounts
- Part 27: Add Security Models
- Part 26: Edit Security Details
- Part 25: View Security Details
- Part 24: Navigating To Details
- Part 23: Getters Validation
- Part 22: Query Parameters
- Part 21: Tab Entries
- Part 20: Tab Header
- Part 19: List View
- Part 18: Vuex Getters
- Part 17: End Domain Model
- Part 16: Start Domain Model
- Part 15: Pop Routes
- Part 14: Push Routes
- Part 13: Removing Accounts
- Part 12: Vuex (Decorators)
- Part 11: Vuex (Accounts)
- Part 10: The App Bar (Settings)
- Part 9: Remove Consumer Rxjs
- Part 8: The App Bar (Back)
- Part 7: Structuring Our App
- Part 6: Animation Between Views
- Part 5: Navigation Fade
- Part 4: Navigation Requests
- Part 3: Fade Animations (cont.)
- Part 2: Fade Animations
- Part 1: Splash Screen
A new view
- root
- src
- views
- Securities.vue
- views
- src
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 new button needs a new icon
- root
- src
- features
- font-awesome.ts
- features
- src
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);
...
Adding the routes enum
- root
- src
- components
- routing
- types.ts
- routing
- components
- src
Next up we need to add a new entry to our routes enum (c).
types.ts
export enum Routes {
...
Securities,
}
Securities Route Entry
- root
- src
- components
- routing
- routing-service.ts
- routing
- components
- src
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,
];
...
}
Securities Button (template)
- root
- src
- components
- main-nav
- TheMainNav.vue
- main-nav
- components
- src
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>
Securities Button (script)
- root
- src
- components
- main-nav
- TheMainNav.vue
- main-nav
- components
- src
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>
The constants for the vuex getters
- root
- src
- store
- store-constants.ts
- store
- src
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";
...
Getter convenience type
- root
- src
- store
- store-types.ts
- store
- src
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>;
...
Defining the shape of our security getters
- root
- src
- store
- security-types.ts
- store
- src
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;
}
...
Time to add the implementation of the getters
- root
- src
- store
- security-module.ts
- store
- src
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);
};
},
};
...
An interface for future use
- root
- src
- store
- store-types.ts
- store
- src
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 { }
...
Adding the getters to our store
- root
- src
- store
- store.ts
- store
- src
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,
});
The getter decorator
- root
- src
- store
- decorators.ts
- store
- src
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
Binding the data
- root
- src
- views
- Securities.vue
- views
- src
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>