#14 Remembering User Navigation Using A Route History
Saturday, September 7, 2019
In this article we will start by introducing a new constant and a few new types to our store types. The constant will make it so that we will not have to use a magic string when using our state decorator when binding the account state to the corresponding field within the accounts view and the types will make it easier when defining the actions and mutations that operate on our state. After that is done we will create a new route state that will be responsible for maintaining a history of the navigations that the user preforms so that we can modify our approach to providing backward navigation functionality. Instead of using the position of the to and from routes within the routing tree we will just follow the history of route changes until we exhaust them when the user clicks on the back button.
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
Say no to magic strings
- root
- src
- store
- store-constants.ts
- store
- src
In the past couple of articles we have used our state decorator to bind an object contained within our vuex state to a field within our vue components. In order to tell the decorator which part of the store we are wanting access to we need to pass in a string. If you would only be accessing the state in a single location I guess passing in a string literal would not make much of a difference but here soon we will be accessing the same state from different locations. To make things a bit more robust we are going to start this article by adding another constant to our store for the only state we currently have (a).
store-constants.ts
...
export const STATE_ACCOUNTS = "accounts";
More types
- root
- src
- store
- store-types.ts
- store
- src
Next up we are going to introduce a few new types to our store so that we can make it a bit easier to both create actions and mutations within our modules and in the case of actions make it easier to declare them within our components (b).
store-types.ts
import {
ActionContext,
ActionTree,
MutationTree,
} from "vuex";
...
export type ActionFn<T> = (payload: T) => void;
export type StoreActionTree = ActionTree<IStoreState, IStoreState>;
export type StoreContext = ActionContext<IStoreState, IStoreState>;
export type StoreMutationTree = MutationTree<IStoreState>;
Using the new types in our account module
- root
- src
- store
- account-module.ts
- store
- src
As we can see in (c), the new types that we have just created makes it a lot easier to create our actions and mutations.
account-module.ts
import {
ActionContext, // <-- remove
ActionTree, // <-- remove
MutationTree, // <-- remove
...
} from "vuex";
...
import {
...
StoreActionTree,
StoreContext,
StoreMutationTree,
} from "@/store/store-types";
...
type AccountContext = ActionContext<IStoreState, IStoreState>; // <-- remove
...
export const actions: StoreActionTree = {
[ACTION_ADD_ACCOUNT](..., { commit }: StoreContext, ...) {
...
},
[ACTION_REMOVE_ACCOUNT](..., { commit }: StoreContext, ...) {
...
},
};
export const mutations: StoreMutationTree = {
...
};
Simplifying our accounts view
- root
- src
- views
- Accounts.vue
- views
- src
And of course we can also use these new types in our accounts view to make things a little nicer to use (d).
Accounts.vue
...
import {
...
ActionFn,
...
STATE_ACCOUNTS,
...
} from "@/store";
...
export default class Accounts extends Vue {
...
@Action(ACTION_ADD_ACCOUNT) private readonly addAccount!: ActionFn<AddAccountPayload>;
@Action(ACTION_REMOVE_ACCOUNT) private readonly removeAccount!: ActionFn<RemoveAccountPayload>;
@State(STATE_ACCOUNTS) private readonly accountState!: IAccountState;
...
}
Adding route types
- root
- src
- store
- route-types.ts
- store
- src
Now that we have updated the code that we have previously added it is now time to add some more. Our goal is to update the behavior of our back button located within the app bar. Currently the route that is considered back is determined by the route tree that we build when creating our routes. This is definitely a workable approach but I would like to switch things up and use the back button as a true back button and have it point back to the route that the user just navigated away from. Our first step in achieving this goal is to add an interface and a few more types (e).
route-types.ts
import { Routes } from "@/components/routing";
export interface IRouteState {
history: Routes[];
}
export type PushRoutePayload = Routes;
Might as well export them now
- root
- src
- store
- index.ts
- store
- src
There would not be much point in creating the interface and types that we just created if they were not going to be needed elsewhere. So as usual to make them easier to import we are going to export them from the index file (f).
index.ts
...
export * from "@/store/route-types";
...
The initial route state
- root
- src
- store
- route-initial-state.ts
- store
- src
Our initial route state is pretty simple to define (g).
route-initial-state.ts
import { IRouteState } from "@/store/route-types";
export const initialState: IRouteState = {
history: [],
};
Updating the store state
- root
- src
- store
- store-types.ts
- store
- src
Before we can create our actions and mutations for the route state we need to update the interface for our store (h).
store-types.ts
...
import { IRouteState } from "@/store/route-types";
export interface IStoreState {
...
routes: IRouteState;
}
...
More constants
- root
- src
- store
- store-constants.ts
- store
- src
Now that we are adding some additional state to our store, unless we want it to remain the same for all time, we are going to need to add some more actions and mutations and for that we are going to need some more constants (i).
store-constants.ts
...
export const ACTION_PUSH_ROUTE = "ACTION_PUSH_ROUTE";
...
export const MUTATION_PUSH_ROUTE = "MUTATION_PUSH_ROUTE";
...
Enter the new route module
- root
- src
- store
- route-module.ts
- store
- src
Next up is to add the actions and mutations that will allow us to modify our route state (j). The additions are fairly simple, for now we just need a way to add a route to the history when the user navigates to a new view. We will be using the history array as a last in first out stack.
route-module.ts
import {
Store,
} from "vuex";
import {
ACTION_PUSH_ROUTE,
MUTATION_PUSH_ROUTE,
} from "@/store/store-constants";
import {
IStoreState,
StoreActionTree,
StoreContext,
StoreMutationTree,
} from "@/store/store-types";
import {
PushRoutePayload,
} from "@/store/route-types";
export const actions: StoreActionTree = {
[ACTION_PUSH_ROUTE](this: Store<IStoreState>, { commit }: StoreContext, route: PushRoutePayload) {
commit(MUTATION_PUSH_ROUTE, route);
},
};
export const mutations: StoreMutationTree = {
[MUTATION_PUSH_ROUTE](state: IStoreState, payload: PushRoutePayload) {
state.routes.history = [payload, ...state.routes.history];
},
};
Life with the spread operator is much nicer
- root
- src
- store
- store.ts
- store
- src
Just as we did with our account initial state and its module we need to add the route initial state and the actions and mutations that we just created to our store (k).
store.ts
...
import { initialState as routeState } from "@/store/route-initial-state";
import {
actions as routeActions,
mutations as routeMutations,
} from "@/store/route-module";
...
const actions = {
...accountActions,
...routeActions,
};
const mutations = {
...accountMutations,
...routeMutations,
};
const state: IStoreState = {
accounts: accountState,
routes: routeState,
};
...
Pushing routes to our history
- root
- src
- components
- routing
- TheRouterOutlet.vue
- routing
- components
- src
The last thing that we want to do for this article is use the action that we just created to add routes to our route history. We can take care of this very easily by just importing the action into the router view and using the from route entry's route property (l).
TheRouterOutlet.vue
...
import {
ACTION_PUSH_ROUTE,
Action,
ActionFn,
PushRoutePayload,
} from "@/store";
...
export default class TheRouterOutlet extends Vue {
@Action(ACTION_PUSH_ROUTE) private readonly pushRoute!: ActionFn<PushRoutePayload>;
...
private animate(route: Routes) {
...
this.pushRoute(from.route);
...
}
...
}