#22 Adding Support To The Routing System For Query Parameters
Saturday, November 2, 2019
In this article we will add the ability for our routing system to handle query parameters. As it stands now all of the routing that we have wanted to perform can be controlled by simply passing a single enum value around. There are at least two different ways that we can approach the problem of adding CRUD operations to our application. One being transmitting the data that we need between views via the store and the other by transmitting the data through query parameters. We are going to opt for using query parameters. So by the time we are done we will no longer use a single enum value for routing but instead we will use a route object that will contain the enum value and query parameters, as well as a couple of other properties. We will finish up by adding a method for extracting query parameters from the query string in a type safe manner.
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
Code Snippets
TheAppBarSettings.vue (2:18)
<script lang="ts">
...
export default class TheAppBarSettings extends Vue {
...
private created() {
...
this.routingService
.navigateBack$
.subscribe(this.close);
}
...
}
</script>
TheAppBar.vue (2:45)
<script lang="ts">
...
export default class TheAppBar extends Vue {
@Inject() private readonly routingService!: RoutingService;
@State(STATE_ROUTES) private readonly routeState!: IRouteState;
private get showBack() { return this.routeState.history.length !== 0; }
private back() {
this.routingService.back();
}
</script>
types.ts (4:22)
import { Dictionary } from "vue-router/types/router";
...
export interface IRouteOptions {
query?: Dictionary<string | Array<string | null>>;
}
export interface IRoute extends IRouteOptions {
id: Routes;
name: string;
}
route-types.ts (5:55)
import { IRoute } from "@/components/routing";
export interface IRouteState {
history: IRoute[];
}
...
export type PushRoutePayload = IRoute;
routing-service.ts (6:26)
...
import {
IRoute,
IRouteOptions,
Routes,
} from "@/components/routing/types";
...
export class RoutingService {
private readonly _navigate = new Subject<IRoute>();
private readonly _navigateBack = new Subject<IRoute>();
...
private readonly _navigateBack$ = this._navigateBack.asObservable();
...
public get history() { return this._store.state[STATE_ROUTES].history; }
public get navigateBack$() { return this._navigateBack$; }
...
public back = () => {
if (this.history.length === 0) {
return;
}
const to = this.history[0];
...
}
...
public createRoute = (to: Routes, options?: IRouteOptions): IRoute => {
return {
id: to,
name: this.find(to).name,
...options,
};
}
...
public navigateTo = (to: Routes, options?: IRouteOptions) => {
if (this.current.isSameRoute(to)) {
return;
}
if (this.history.length > 0 && this.history[0].id === to) {
this.back();
return;
}
this._navigate.next(this.createRoute(to, options));
this._routeChanged.next();
}
public queryString = (route: IRoute) => {
return typeof(route.query) !== "undefined"
? `?${Object.keys(route.query)
.map((x) => `${x}=${route.query![x]}`)
.join("&")}`
: "";
}
...
}
Advertisement
MainNavButton.vue (11:56)
<script lang="ts">
...
import { IRoute, ... } from "@/components/routing";
...
export default class MainNavButton extends Vue {
...
private setActive(route: IRoute) {
this.isActive = this.route === route.id;
}
}
</script>
TheRouterOutlet.vue (12:31)
<script lang="ts">
...
import {
IRoute,
IRouteOptions,
RouteEntry,
Routes,
RoutingService,
} from "@/components/routing";
...
export default class TheRouterOutlet extends Vue {
...
private toRoute!: IRoute;
...
private animate(route: IRoute, ...) {
...
this.toEntry = this.routingService.find(route.id);
this.toRoute = route;
...
}
private animateBack(route: IRoute) {
...
}
private animateForward(route: IRoute) {
...
}
...
private animationCompleteOut() {
...
this.$router.push(`${this.toEntry.path}${this.routingService.queryString(this.toRoute)}`);
...
}
...
private navigateBack(route: IRoute) {
...
}
private navigateForward(route: IRoute) {
const current = this.routingService.current;
this.pushRoute({
id: current.route,
name: current.name,
query: this.$route.query,
});
...
}
}
</script>
SecuritiesDetails.vue (15:59)
<template lang="pug">
div Securities Details
</template>
SecuritiesDetails.vue (16:07)
<script lang="ts">
import { Component, Inject, Vue } from "vue-property-decorator";
import { RoutingService } from "@/components/routing";
@Component
export default class SecuritiesDetails extends Vue {
@Inject() private readonly routingService!: RoutingService;
private mounted() {
}
}
</script>
types.ts (17:17)
...
export enum Routes {
...
SecuritiesDetails,
}
...
routing-service.ts (17:38)
...
const securitiesDetails = new RouteEntry({
component: () => import(/* webpackChunkName: "securities-details" */ "../../views/SecuritiesDetails.vue"),
name: "securities-details",
parent: securities,
path: "/securities-details",
route: Routes.SecuritiesDetails,
});
export class RoutingService {
...
private readonly _routes = [
...
securitiesDetails,
];
...
}
routing-service.ts (21:00)
...
export class RoutingService {
...
public queryParam<Q, R extends string | number = string>(
func: (q: Q) => string, transform: (x: string) => R = (x) => x as R) {
const param = func((this.router.currentRoute.query as unknown) as Q);
return transform(param);
}
...
}
SecuritiesDetails.vue (23:40)
<script lang="ts">
...
interface IQuery {
id: string;
which: string;
}
...
export default class SecuritiesDetails extends Vue {
...
private mounted() {
console.log(this.$router.currentRoute.query);
const id = this.routingService.queryParam<IQuery, number>((x) => x.id, parseInt);
const which = this.routingService.queryParam<IQuery>((x) => x.which);
console.log(`${typeof(id)} ${id}`);
console.log(`${typeof(which)} ${which}`);
}
}
</script>
Exciton Interactive LLC