Angular Pseudo-Events

Shijir Tsogoo
Jul 31, 2018 · 5 min read

Angular offers a nifty little feature to facilitate the process of listening to keyboard events. Even though it’s mentioned in Angular’s template binding documentation very briefly as pseudo-event, it hasn’t been further documented anywhere else. Before anything else, to see what problem Angular pseudo-event solves, let’s take a look at this simple task where you need to add an UNDO command to a toggle-switch checkbox. The UNDO command should revert the user’s last choice.

Just like how we execute the UNDO command on our computers, the command should respond to CTRL+Z key combination. The example below shows how we can accomplish the task without pseudo-events:

As you can see in the example above, I am looking at the KeyboardEvent.key to check which keystroke is hit. There are also other properties of KeyboardEvent that you can use to check and catch keystrokes such as KeyboardEvent.key, KeyboardEvent.charCode, KeyboardEvent.keyCode or KeyboardEvent.which. However, some of them have been deprecated or browser support for them will vary from one browser to another. Also, the more key combinations we need to listen to, the more convoluted the syntax becomes.

Angular pseudo-events will free you from all the concerns above. Through pseudo-events, Angular allows you to directly bind to a specific keystroke or keystroke combination. That means a keyboard event will be only fired on that specific key or key combination instead of on all key events. The following is an example of how to declare pseudo-events in your template:

<input (keyup.enter)='...responds to enter...' /><input (keydown.esc)='...responds to escape...' /><input (keyup.shift.f)='...responds to shift+f...' />

Now let’s see how Pseudo-Events can facilitate us to listen to CTRL+Z key combination:

As you can see, we now no longer need to check which keystrokes are registered because we specify the combination of keys that our handler should respond to, and the syntax has become more declarative.

Using Angular Pseudo-Events in @HostListener

Just like any other DOM events, you can listen to pseudo-events through @HostListener:

@HostListener('keydown.control.z') undo(event: KeyboardEvent) {

// responds to control+z
}

Of course, if a host element is not focusable or you need to catch a keyboard event regardless of where it originated, you could bind the event to a global element like this:

@HostListener('document:keydown.control.z') undo(event: KeyboardEvent) {

// responds to control+z
}

Key names in Pseudo-Events

Now take a look at this example of a pseudo-event key combination:

<input (keydown.control.shift.z)='...'/>

You might be wondering where these key names such as control, shift, and z are coming from. They are not unique to Angular pseudo-events. In fact, they are case-insensitive key property values of KeyboardEvent. If you want to see a comprehensive list of keyboard event key property values, please see this reference. Now let’s go over the key values that are available in Angular pseudo-events.

Modifier Keys

Modifier keys include Shift, Control, Alt(Option), and Meta(Command). I will refer to any other key as a non-modifier key from here on. Modifier key pseudo-events look like this:

<input (keyup.control)='...respond to ctrl/control...' /><input (keyup.alt)='...respond to alt/option...' /><input (keyup.shift)='...respond to shift...' /><input (keyup.meta)='...respond to command...' />

There are several points that you need to keep in mind to use modifier keys in Angular pseudo-events. First, any key combination must have at least one modifier key, but only one non-modifier key. For example, the following case wouldn’t work as the combination consists of only letter keys:

<input (keyup.a.z)='...respond to a+z...' />

Second, a non-modifier key must be always defined at the very end of the key combination. The following is a proper placement of a non-modifier key as the Z (non-modifier key) is defined at the end:

<input (keydown.control.z)='...responds to control+z...' />

In contrast, the example below shows an improper placement of a modifier key:

<input (keydown.z.control)='...won't respond at all...' />

Third, the sequence of modifier keys themselves doesn’t matter. So you can place them in any order that suits your needs. Both of the examples below show valid use cases:

<input (keydown.shift.control.z)='...responds to shift+control+z...' />
<input (keydown.control.shift.z)='...responds to control+shift+z...' />

Let’s move onto the examples of non-modifier keys.

Functional keys

<input (keyup.f5)='...responds to F5...' /><input (keyup.f12)='...responds to F12...' />

Number and letter keys

<input (keyup.0)='...responds to 0...' />

<input (keyup.9)='...responds to 9...' />
<input (keyup.a)='...responds to a...' />

<input (keyup.z)='...responds to z...' />

Arrow keys

<input (keyup.arrowup)='...responds to arrowup...' />

<input (keyup.arrowright)='...responds to arrowright...' />

Other keys

<input (keyup.enter)='...responds to enter...' /><input (keyup.esc)='...responds to escape...' /> <input (keyup.escape)='...responds to escape...' />

<input (keyup.backspace)='...responds to backspace...' />
<input (keyup.tab)='...responds to tab...' />

Currently, there are only two keys that are an exception to this: Dot and Space. When you hit the dot key on your keyboard, its KeyboardEvent.key property value would be ".". But, as you can imagine, it would be syntactically improper to use it in a pseudo-event as dots function as a separator in key combinations. That’s why it’s mapped to the "dot" keyword. So you would do the following to properly listen to the Dot keystroke:

<input (keydown.dot)='...responds to dot...'/>

…instead of:

<input (keydown..)='...won't respond at all...'/>

The same thing goes for the Space keystroke. As its KeyboardEvent.key property value is " " , it’s mapped to the "space" keyword for better readability. So rather than this:

<input (keydown. )='...won't respond at all...'/>

You would define the key as below:

<input (keydown.space)='...won't respond at all...'/>

Unfortunately, Angular pseudo-events are still missing this kind of mapping for most of the symbol keys such as Minus, Equal, Slash, BracketLeft, BracketRight, Backquote, etc. As they are symbol keys, they result in very poor readability or sometimes even break the binding itself. For example, if I want to listen the Minus keystroke, I can only do that as follows:

<input (keydown.-)='...responds to minus...'/>

As you can see, it looks rather odd or syntactically wrong, but it works. It would be more readable or at least easier on the eyes if it were mapped to its declarative keyword:"minus". How about the Equal keystroke?

<input (keydown.=)='...breaks the binding...'/>

If you try to listen to the Equal keystroke as shown above, it will break your template binding because there is no "equal" keyword mapping for it either.

Despite a few minor shortcomings with symbol keys, Angular pseudo-events are an awesome feature that covers most of the use cases of listening to keyboard events. I believe that using it makes the process of implementing keyboard accessibility and interactivity much easier in any Angular application.

After reading this blog post, I hope you have gained a few ideas and insights about Angular pseudo-events. On a related note, if you are interested in learning more about events and different ways of listening to them in Angular, I have started a blog series on them. Checkout the first post of that series here: Four ways of listening to DOM events in Angular (Part 1: Event Binding)

Clarity Design System

Clarity is an open source design system that brings together UX guidelines, an HTML/CSS framework, and Angular components. Visit our website here: http://clarity.design

Thanks to Jeremy Wilken, Scott Mathis, Aditya Bhandari, Lilia Kim, Jeeyun Lim, and Matt Hippely.

Shijir Tsogoo

Written by

Clarity Team Member

Clarity Design System

Clarity is an open source design system that brings together UX guidelines, an HTML/CSS framework, and Angular components. Visit our website here: http://clarity.design