Angular hotkeys (we all love shortcuts!)


Let’s review how to implement hotkeys (shortcuts) on Angular apps.

On this post, we will use a library called angular2-hotkeys.

First, we will walk through the implementation and some extra configuration tips to finally go over some pros and cons as part of a conclusion.

To understand better, we will create a simple app with

  • an input
  • a save button with a shortcut command+s/control+s (mac/win)

Installation npm install angular2-hotkeys --save

Important If you get this error

ERROR in ../node_modules/angular2-hotkeys/src/hotkeys.service.d.ts:9:16 - error TS2304: Cannot find name 'MousetrapInstance'.
9     mousetrap: MousetrapInstance;

There is a PR not merged yet with the fix. Also, there is a workaround (it’s on the repo used on this example) until the PR is merged and the new version is released: on devDependencies include "@types/mousetrap": "1.6.3"

Update app.module.ts

import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser';
import { HotkeyModule } from 'angular2-hotkeys';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';

@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    AppRoutingModule,
    CommonModule,
    ReactiveFormsModule,
    HotkeyModule.forRoot(), // adding HotkeysModule
  ],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}

Creating a component and adding a shortcut to save in the constructor

  constructor(private hotkeysService: HotkeysService) {
    this.hotkeysService.add(
      new Hotkey(
        'command+s', // key combination
        (): boolean => { // callback function to execute after key combination
          this.save(); 
          return false; // prevent bubbling
        },
        ['INPUT', 'TEXTAREA', 'SELECT'], // allow shortcut execution in these html elements
        'save' // shortcut name
      )
    );
  }

As we can see, hotkeysService allows us to add a new HotKey shortcut. Let’s take a look into the class structure

export declare class Hotkey {
    combo: string | string[];
    callback: (event: KeyboardEvent, combo: string) => ExtendedKeyboardEvent | boolean;
    allowIn?: string[];
    description?: string | Function;
    action?: string;
    persistent?: boolean;
    private formattedHotkey;
    static symbolize(combo: string): string;
    /**
     * Creates a new Hotkey for Mousetrap binding
     *
     * @param combo       mousetrap key binding
     * @param description description for the help menu
     * @param callback    method to call when key is pressed
     * @param action      the type of event to listen for (for mousetrap)
     * @param allowIn     an array of tag names to allow this combo in ('INPUT', 'SELECT', and/or 'TEXTAREA')
     * @param persistent  if true, the binding is preserved upon route changes
     */
    constructor(combo: string | string[], callback: (event: KeyboardEvent, combo: string) => ExtendedKeyboardEvent | boolean, allowIn?: string[], description?: string | Function, action?: string, persistent?: boolean);
    get formatted(): string[];
}

We included allowInput with INPUT in order to get our combo working even when we have focus on it. If we don’t add it, since command+s is a browser shortcut to save the page, our shortcut will not be executed and instead, the save file prompt will appear (you can try removing allowIn parameter and trying to run the combo)

Now we will add a formGroup and some logic to support

  • command+s on Mac
  • control+s on Windows
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';
import { Hotkey, HotkeysService } from 'angular2-hotkeys';

@Component({
  selector: 'app-my-form-with-hot-keys',
  templateUrl: './my-form-with-hot-keys.component.html',
  styleUrls: ['./my-form-with-hot-keys.component.scss'],
})
export class MyFormWithHotKeysComponent implements OnInit {
  mac = 'command+s';
  win = 'ctrl+s';
  isMac = navigator.platform.includes('Mac');
  saveCommand = this.isMac ? this.mac : this.win;
  saveCommandTitle = this.isMac ? '⌘+s' : this.win;
  form: FormGroup;
  constructor(private hotkeysService: HotkeysService, private fb: FormBuilder) {
    this.hotkeysService.add(
      new Hotkey(
        this.saveCommand, //  key combination
        (): boolean => {
          // callback function to execute after key combination
          this.save();
          return false; // prevent bubbling
        },
        ['INPUT', 'TEXTAREA', 'SELECT'], // allow shortcut execution in these html elements
        'save' // shortcut name
      )
    );
  }

  ngOnInit(): void {
    this.configForm();
  }

  save() {
    alert(this.form.controls?.textValue?.value || 'no text');
  }

  private configForm() {
    this.form = this.fb.group({
      textValue: '',
    });
  }
}

adding the template

<div class="form" [formGroup]="form">
  <div class="title">hot keys</div>
  <div>
    <label for="textValue" class="text-label">Type some text</label>
    <input
      type="text"
      id="textValue"
      formControlName="textValue"
      class="input-form"
    />
  </div>
  <div>
    <button (click)="save()" title="{{ saveCommandTitle }}">save</button>
  </div>
</div>

Let’s see how it looks so far Alt Text

Nice! it’s working, we can save clicking the save button or running the shortcut!

Cheatsheet The library supports a cheatsheet with a list of all shortcuts registered in our app, the only thing we need to do is add

<hotkeys-cheatsheet></hotkeys-cheatsheet>

We will add it in app.component.html (we can pass a custom title)

<hotkeys-cheatsheet title="Hotkeys map"></hotkeys-cheatsheet>
<app-my-form-with-hot-keys></app-my-form-with-hot-keys>

By default, to see the cheatsheet, we need to press ?. We will override default values to see what else we can setup. You can check the comments added on each property.

For example, instead of using ? to show the cheatsheet, we will use a !

In app.module.ts, we will pass options using the interface IHotkeyOptions.

Important: Only shortcuts with description will appear in the cheatsheet. Look at this to see how the component works inside the library

import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser';
import { HotkeyModule, IHotkeyOptions } from 'angular2-hotkeys';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { MyFormWithHotKeysComponent } from './components/my-form-with-hot-keys/my-form-with-hot-keys.component';

const options: IHotkeyOptions = {
  disableCheatSheet: false, // disable the cheat sheet popover dialog? Default: false
  cheatSheetHotkey: '!', // key combination to trigger the cheat sheet. Default: '?'
  cheatSheetCloseEsc: true, // use also ESC for closing the cheat sheet. Default: false
  cheatSheetCloseEscDescription: 'hide hotkeys map', // description for the ESC key for closing the cheat sheet (if enabed). Default: 'Hide this help menu'
  cheatSheetDescription: 'show all hotkeys', // description for the cheat sheet hot key in the cheat sheet. Default: 'Show / hide this help menu'
};

@NgModule({
  declarations: [AppComponent, MyFormWithHotKeysComponent],
  imports: [
    BrowserModule,
    AppRoutingModule,
    CommonModule,
    ReactiveFormsModule,
    HotkeyModule.forRoot(options), // adding options instance when registering module
  ],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}

Finally, typing ! the cheatsheet is displayed

Alt Text

Conclusions Pros

  • The library is easy to implement and offers some handy features
  • Cheatsheet is cool!

Cons

  • Last release was on march this year and even there are some PRs with important fixes including angular 8+ support the release is still pending

Thanks for reading! If you like this post, give it a 🦄 ❤️ 🔖.

References