Creating states with keymap

So far, what we have declared in the states (or routes) is the path and the component that we want to go with them. What Angular does allow us to do is add a new property called data to the route configuration. This allows us to add any data that we would like regarding any route. In our case, it works out very well because we want to be able to toggle routes based on the keys a user presses.

So, let's take an example route that we have defined previously:

import { HomeComponent } from './home.component';

export const HomeRoutes = [
{ path: 'home', component: HomeComponent },
];

export const HomeComponents = [
HomeComponent
];

We will now modify this and add the new data property to the route configuration:

import { HomeComponent } from './home.component';

export const HomeRoutes = [
{ path: 'home', component: HomeComponent, data: { keymap: 'ctrl+h'} },
];

export const HomeComponents = [
HomeComponent
];

You can see that we have added a property called keymap and its value ctrl+h; we will do the same for all the other routes defined as well. One important thing to nail down in the very beginning is the anchor key (ctrl, in this case) that is going to be used alongside a secondary identifying key (h for the home route). This really helps filter down key presses that the user may making within your application.

Once we have the keymaps associated with each of the routes, we can register all of these keymaps when the app loads and then start tracking user activity to determine whether they have selected any of our predefined keymaps.

To register the keymaps, in the app.component.ts file, we will first define the Map in which we are going to hold all the data and then extract the data from the routes before adding it to Map:

import {Component} from '@angular/core';
import {Router} from "@angular/router";

@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss', './theme.scss']
})
export class AppComponent {

// defined the keyMap
keyMap = new Map();

constructor(private router: Router) {
// loop over the router configuration
this.router.config.forEach((routerConf)=> {

// extract the keymap
const keyMap = routerConf.data ? routerConf.data.keymap :
undefined;

// if keymap exists for the route and is not a duplicate,
add
// to master list
if (keyMap && !this.keyMap.has(keyMap)) {
this.keyMap.set(keyMap, `/${routerConf.path}`);
}
})
}

}

Once the data is added to the keyMap, we will need to listen to user interactions and determine where the user wants to navigate. To do that, we can use the @HostListener decorator provided by Angular, listen for any keypress events, and then filter item down based on the application's requirements, as follows:

import {Component, HostListener} from '@angular/core';
import {Router} from "@angular/router";

@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss', './theme.scss']
})
export class AppComponent {

// defined the keyMap
keyMap = new Map();

// add the HostListener
@HostListener('document:keydown', ['$event'])
onKeyDown(ev: KeyboardEvent) {

// filter out all non CTRL key presses and
// when only CTRL is key press
if (ev.ctrlKey && ev.keyCode !== 17) {

// check if user selection is already registered
if (this.keyMap.has(`ctrl+${ev.key}`)) {

// extract the registered path
const path = this.keyMap.get(`ctrl+${ev.key}`);

// navigate
this.router.navigateByUrl(path);
}
}
}

constructor(private router: Router) {
// loop over the router configuration
this.router.config.forEach((routerConf)=> {

// extract the keymap
const keyMap = routerConf.data ? routerConf.data.keymap :
undefined;

// if keymap exists for the route and is not a duplicate,
add
// to master list
if (keyMap && !this.keyMap.has(keyMap)) {
this.keyMap.set(keyMap, `/${routerConf.path}`);
}
})
}
}

There we have it! We can now define and navigate to routes easily whenever a user makes a keypress. However, before we move on, we will need to take another perspective here to understand the next step. Consider you are the end user and not the developer. How do you know what the bindings are? What do you do when you want to bind not just the routes on a page but also the buttons? How do you know whether you are pressing the wrong keys?

All of this can be fixed with a very simple UX review of what we have so far and what we need instead. One thing that is clear is that we need to show the user what they are selecting so that they do not keep pounding our application with incorrect key combinations.

First, to inform our users of what they can select, let's modify the navigation in such a way that the first character of each of the route names is highlighted. Let's also create a variable to hold the value that the user is selecting, display that on the UI, and clear it out after a few milliseconds.

We can modify our app.component.scss to that effect, as follows:

.active {
color: red;
}

nav {
button {
&::first-letter {
font-weight:bold;
text-decoration: underline;
font-size: 1.2em;
}
}
}

.bottom-right {
position: fixed;
bottom: 30px;
right: 30px;
background: rgba(0,0,0, 0.5);
color: white;
padding: 20px;
}

Our template gets an addition at the very end to show the key the user has pressed:

<nav>
<button mat-button
routerLink="/about"
routerLinkActive="active">
About
</button>
<button mat-button
routerLink="/dashboard"
routerLinkActive="active">
Dashboard
</button>
<button mat-button
routerLink="/home"
routerLinkActive="active">
Home
</button>
<button mat-button
routerLink="/profile"
routerLinkActive="active">
Profile
</button>
</nav>

<router-outlet></router-outlet>

<section [class]="keypress? 'bottom-right': ''">
{{keypress}}
</section>

Our app.component.ts in its final form is as follows:

import {Component, HostListener} from '@angular/core';
import {Router} from "@angular/router";

@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss', './theme.scss']
})
export class AppComponent {

// defined the keyMap
keyMap = new Map();

// defined the keypressed
keypress: string = '';

// clear timer if needed
timer: number;

// add the HostListener
@HostListener('document:keydown', ['$event'])
onKeyDown(ev: KeyboardEvent) {

// filter out all non CTRL key presses and
// when only CTRL is key press
if (ev.ctrlKey && ev.keyCode !== 17) {

// display user selection
this.highlightKeypress(`ctrl+${ev.key}`);

// check if user selection is already registered
if (this.keyMap.has(`ctrl+${ev.key}`)) {

// extract the registered path
const path = this.keyMap.get(`ctrl+${ev.key}`);

// navigate
this.router.navigateByUrl(path);
}
}
}

constructor(private router: Router) {
// loop over the router configuration
this.router.config.forEach((routerConf)=> {

// extract the keymap
const keyMap = routerConf.data ? routerConf.data.keymap :
undefined;

// if keymap exists for the route and is not a duplicate,
add
// to master list
if (keyMap && !this.keyMap.has(keyMap)) {
this.keyMap.set(keyMap, `/${routerConf.path}`);
}
})
}

highlightKeypress(keypress: string) {
// clear existing timer, if any
if (this.timer) {
clearTimeout(this.timer);
}

// set the user selection
this.keypress = keypress;

// reset user selection
this.timer = setTimeout(()=> {
this.keypress = '';
}, 500);
}

}

This way, the user is always informed of their options and what they are selecting, making the overall usability of your application a lot higher.

Irrespective of what the user selects, they will always see their selection at the bottom-right side of the screen as long as the Ctrl key is pressed.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.17.244