#4 Communicating a Navigation Request to the Router Outlet
Sunday, June 30, 2019
In this article we will start by creating a route entry object that initially just mimics the route config object from vue router. Once that is in place we will create a routing service that we will be able to inject into any of our views and components that need to make a routing request. This service will be our single point of communication between those views and components and a router outlet component that we will also be creating. The router outlet will be responsible for acting on the navigation request by performing the appropriate animations, such as translating the view in and and out, and actually performing the route change using the vue router.
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
Leading underscore
- root
- tslint.json
First up is a quick change to our tslint configuration so that we do not have to deal with any complaints about variable names with a leading underscore (a).
tslint.json
{
...,
"rules": {
...,
"variable-name": [
true,
"allow-leading-underscore"
]
}
}
Mimicking a route config object
- root
- src
- components
- routing
- route-entry.ts
- routing
- components
- src
Next step in adding additional functionality that we want to the routing system is
to replace the elements of the routing system that were added for us during project
creation. We will start by first defining a route entry (b)
that will contain all of the information that we need as well as provide a
config
getter that will create a route config
object defined by vue router.
route-entry.ts
import Vue, { VueConstructor } from "vue";
import { RouteConfig } from "vue-router";
type VuePromiseFn = () => Promise<typeof import("*.vue")>;
export interface IRouteEntryConfig {
component: VueConstructor<Vue> | VuePromiseFn;
name: string;
path: string;
}
export class RouteEntry {
private readonly _component: VueConstructor<Vue> | VuePromiseFn;
private readonly _name: string;
private readonly _path: string;
public get component() { return this._component; }
public get config(): RouteConfig {
return {
component: this.component,
name: this.name,
path: this.path,
};
}
public get name() { return this._name; }
public get path() { return this._path; }
constructor(config: IRouteEntryConfig) {
this._component = config.component;
this._name = config.name;
this._path = config.path;
}
}
The routes enum
- root
- src
- components
- routing
- types.ts
- routing
- components
- src
To avoid having magic strings in the project we will now introduce my usual solution which is the enum (c).
types.ts
export enum Routes {
About,
Home,
}
The glue that holds it all together
- root
- src
- component
- routing
- routing-service.ts
- routing
- component
- src
In order for us to indicate that we wish to navigate to a different route from a view or component and have the correct animation play before and after the navigation we will need a way to communicate that intention. To do this we will use a service (d) that will effectively be a singleton within our application.
routing-service.ts
import Vue from "vue";
import Router from "vue-router";
import { RouteEntry } from "@/components/routing/route-entry";
import { Routes } from "@/components/routing/types";
import Home from "@/views/Home.vue";
Vue.use(Router);
const home = new RouteEntry({
component: Home,
name: "home",
path: "/",
});
const about = new RouteEntry({
component: () => import(/* webpackChunkName: "about" */ "../../views/About.vue"),
name: "about",
path: "/about",
});
export class RoutingService {
private readonly _routes = new Map<Routes, RouteEntry>([
[Routes.About, about],
[Routes.Home, home],
]);
private readonly _values = Array.from(this._routes.values());
private readonly _router = new Router({
base: process.env.BASE_URL,
mode: "history",
routes: this._values.map((x) => x.config ),
});
public get router() { return this._router; }
}
Registering our routing service
- root
- src
- main.ts
- src
Now that we have a routing service that we want to provide to the rest of our application we only need to register it in our main file (e). By adding it to the provide object we will be able to inject it into any view or component that needs it.
main.ts
import Vue from "vue";
import App from "./App.vue";
import { RoutingService } from "@/components/routing/routing-service";
import store from "./store";
import "./registerServiceWorker";
Vue.config.productionTip = false;
const routingService = new RoutingService();
new Vue({
provide: {
routingService,
},
router: routingService.router,
store,
render: (h) => h(App),
}).$mount("#app");
The manager for our route requests
- root
- src
- components
- routing
- TheRouterOutlet.vue
- routing
- components
- src
At this point we have a way for our views and components to communicate that a route change is needed but we do not have anything that will perform those requests. That means it is time for our router outlet template (f), script (g), and style (h).
TheRouterOutlet.vue
<template lang="pug">
router-view.router-view/
</template>
TheRouterOutlet.vue
<script lang="ts">
import { Component, Vue } from "vue-property-decorator";
@Component
export default class TheRouterOutlet extends Vue {
}
</script>
TheRouterOutlet.vue
<style lang="sass" scoped>
.router-view
height: 100%
</style>
Adding the router outlet to our app
- root
- src
- App.vue
- src
Of course adding our router outlet is as simple as adding our splash screen. We first need to update our template (i) and quickly following that up by updating the class (j).
App.vue
<template lang="pug">
div#app
SplashScreen/
div#nav
router-link(to="/") Home
router-link(to="/about") About
RouterOutlet/
</template>
App.vue
<script lang="ts">
...
import RouterOutlet from "@/components/routing/TheRouterOutlet.vue";
...
@Component({
components: {
RouterOutlet,
...,
},
})
export default class App extends Vue {
}
</script>
Adding that communication ability I have been talking about
- root
- src
- components
- routing
- RoutingService.ts
- routing
- components
- src
Right now we have the routing service that we can use to communicate with but it
currently does not have any means for perform that communication. We are going
to fix this now by using some more rxjs
(k).
RoutingService.ts
...
import { Subject } from "rxjs";
...
export class RoutingService {
private readonly _navigate = new Subject<Routes>();
private readonly _navigate$ = this._navigate.asObservable();
...
public get navigate$() { return this._navigate$; }
...
public complete = () => {
this._navigate.complete();
}
public navigateTo = (to: Routes) => {
this._navigate.next(to);
}
}
Exporting for easier importing
- root
- src
- components
- routing
- index.ts
- routing
- components
- src
Now that we have the essentials in place we can make them easier to use by exporting them from and index file (l).
index.ts
export * from "@/components/routing/route-entry";
export * from "@/components/routing/routing-service";
export * from "@/components/routing/types";
Sending a route request
- root
- src
- views
- About.vue
- views
- src
To test that everything is working the way we want it to we can update our about view to make a navigation request when it is mounted (m).
About.vue
<script lang="ts">
import { Component, Inject, Vue } from "vue-property-decorator";
import { Routes, RoutingService } from "@/components/routing";
@Component
export default class About extends Vue {
@Inject() private readonly routingService!: RoutingService;
private mounted() {
this.routingService.navigateTo(Routes.Home);
}
}
</script>
Is anybody listening?
- root
- src
- components
- routing
- TheRouterOutlet.vue
- routing
- components
- src
Now we have a navigation request being made but currently nothing is listening for it. Making a few small changes to our router outlet we are now able to hear the requests and we will be able to perform the appropriate actions (n).
TheRouterOutlet.vue
<script lang="ts">
import { ..., Inject, ... } from "vue-property-decorator";
import { Routes, RoutingService } from "@/components/routing";
@Component
export default class TheRouterOutlet extends Vue {
@Inject() private readonly routingService!: RoutingService;
private beforeDestroy() {
this.routingService.complete();
}
private created() {
this.routingService
.navigate$
.subscribe(x => console.log(Routes[x]));
}
}
</script>