Native Key-Combination Event-Binding Support In Angular 2 Beta 17
Last week, I experimented with dynamically parsing and binding DOM (Document Object Model) events using the EVENT_MANAGER_PLUGINS system. While that post more of a fun exploration of the Angular 2 template syntax and plugin system, I didn't realize that Angular 2 Beta 17 actually ships with native key-combination support. So, in essence, just about everything that I implemented in my previous post is already available by default in the Browser platform.
Run this demo in my JavaScript Demos project on GitHub.
From what I can see, this appears to be an all-but-undocumented feature. It is touched upon briefly in the "Basics: User Input" documentation; but, there is very little explanation of either the breadth or the limitations of the key-combination support. So, digging through the actual source code, here's what I can find.
First, the limitations. The native KeyEventsPlugin plugin only support keydown and keyup events, not keypress. And, these key combinations can only be bound to a specific element (or host) - the plugin doesn't appear to support the global "document:" or "window:" event-scope. There is also no implicit support for browser-overrides. Meaning, if you need to cancel the default-behavior of the key-combination, you have to do it yourself (with $event.preventDefault()).
Now, the features. The syntax for the key binding is very similar to what I had in my previous post. You basically start with the event-type and then add a series of dot-delimited modifiers. For example:
- keydown.a
- keydown.b
- keydown.c
- keydown.dot
- keydown.Spacebar
- keydown.meta.Enter
- keydown.alt.Enter
- keydown.control.Enter
- keydown.shift.Enter
- keydown.meta.o
- keydown.meta.s
- keydown.meta.f
- keydown.escape
The "special key" modifiers are:
- alt
- control
- meta - The Command key on Mac and the Windows key on Windows.
- shift
There are then two replacement keys that are there just keep the syntax from breaking:
- Space - Or, you can use "Spacebar".
- Dot - Since the modifiers are dot-delimited.
Beyond that, the values in your event-type are either character literals or special values taken from the "key" property of the event object (ex, "ArrowRight" and "PageDown").
To see this in action, I've create a simple demo in which you can focus an Input (remember this doesn't work on the global event scope) and try a number of the native key-combinations:
<!doctype html> | |
<html> | |
<head> | |
<meta charset="utf-8" /> | |
<title> | |
Native Key-Combination Event Binding In Angular 2 Beta 17 | |
</title> | |
<link rel="stylesheet" type="text/css" href="./demo.css"></lin> | |
</head> | |
<body> | |
<h1> | |
Native Key-Combination Event Binding In Angular 2 Beta 17 | |
</h1> | |
<my-app> | |
Loading... | |
</my-app> | |
<!-- Load demo scripts. --> | |
<script type="text/javascript" src="../../vendor/angularjs-2-beta/17/es6-shim.min.js"></script> | |
<script type="text/javascript" src="../../vendor/angularjs-2-beta/17/Rx.umd.min.js"></script> | |
<script type="text/javascript" src="../../vendor/angularjs-2-beta/17/angular2-polyfills.min.js"></script> | |
<script type="text/javascript" src="../../vendor/angularjs-2-beta/17/angular2-all.umd.js"></script> | |
<!-- AlmondJS - minimal implementation of RequireJS. --> | |
<script type="text/javascript" src="../../vendor/angularjs-2-beta/17/almond.js"></script> | |
<script type="text/javascript"> | |
// Defer bootstrapping until all of the components have been declared. | |
requirejs( | |
[ /* Using require() for better readability. */ ], | |
function run() { | |
ng.platform.browser.bootstrap( require( "App" ) ); | |
} | |
); | |
// --------------------------------------------------------------------------- // | |
// --------------------------------------------------------------------------- // | |
// I provide the root application component. | |
define( | |
"App", | |
function registerApp() { | |
// Configure the App component definition. | |
ng.core | |
.Component({ | |
selector: "my-app", | |
// In the following template, we are binding to the key-event | |
// support that comes with Angular 2 out-of-the-box. This allows | |
// us to bind to specific key combinations, including modifiers | |
// and key-identifiers as per the specification: | |
// -- | |
// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key | |
// -- | |
// NOTE: This only works with keydown and keyup - NOT keypress. | |
// -- | |
// CAUTION: The built-in key event plugin does not support | |
// binding to the GLOBAL scope for some reason (ie, document: or | |
// window:). As such, it can only be bound to a specific element. | |
template: | |
` | |
<p> | |
Focus the Input and then type | |
<em>(binding not supported on global scope)</em>. | |
</p> | |
<input | |
(keydown.Enter)="handleKeyEvent( $event, 'Enter' )" | |
(keydown.alt.Enter)="handleKeyEvent( $event, 'ALT + Enter' )" | |
(keydown.control.Enter)="handleKeyEvent( $event, 'Control + Enter' )" | |
(keydown.meta.Enter)="handleKeyEvent( $event, 'Meta + Enter' )" | |
(keydown.shift.Enter)="handleKeyEvent( $event, 'Shift + Enter' )" | |
(keydown.Escape)="handleKeyEvent( $event, 'Escape' )" | |
(keydown.ArrowLeft)="handleKeyEvent( $event, 'Arrow Left' )" | |
(keydown.ArrowUp)="handleKeyEvent( $event, 'Arrow Up' )" | |
(keydown.ArrowRight)="handleKeyEvent( $event, 'Arrow Right' )" | |
(keydown.ArrowDown)="handleKeyEvent( $event, 'Arrow Down' )" | |
(keydown.Dot)="handleKeyEvent( $event, 'Dot' )" | |
(keydown.Space)="handleKeyEvent( $event, 'Space' )" | |
(keydown.shift)="handleKeyEvent( $event, 'Shift' )" | |
(keydown.meta.b)="handleKeyEvent( $event, 'Meta + b' )" | |
(keydown.meta.o)="handleKeyEvent( $event, 'Meta + o' )" | |
(keydown.meta.s)="handleKeyEvent( $event, 'Meta + s' )" | |
(keydown.meta.i)="handleKeyEvent( $event, 'Meta + i' )" | |
(keydown.meta.p)="handleKeyEvent( $event, 'Meta + p' )" | |
(keydown.meta.f)="handleKeyEvent( $event, 'Meta + f' )" | |
(keydown.h)="handleKeyEvent( $event, 'H' )" | |
(keydown.e)="handleKeyEvent( $event, 'E' )" | |
(keydown.l)="handleKeyEvent( $event, 'L' )" | |
(keydown.o)="handleKeyEvent( $event, 'O' )" | |
(keydown.1)="handleKeyEvent( $event, '1' )" | |
(keydown.2)="handleKeyEvent( $event, '2' )" | |
(keydown.3)="handleKeyEvent( $event, '3' )" | |
(keydown.4)="handleKeyEvent( $event, '4' )" | |
(keydown.5)="handleKeyEvent( $event, '5' )" | |
autofocus> | |
<ul> | |
<li>Enter</li> | |
<li>ALT + Enter</li> | |
<li>Control + Enter</li> | |
<li>Meta + Enter</li> | |
<li>Shift + Enter</li> | |
<li>Escape</li> | |
<li>Arrow Left</li> | |
<li>Arrow Up</li> | |
<li>Arrow Right</li> | |
<li>Arrow Down</li> | |
<li>Dot</li> | |
<li>Space</li> | |
<li>Shift</li> | |
<li>Meta + b</li> | |
<li>Meta + o</li> | |
<li>Meta + s</li> | |
<li>Meta + i</li> | |
<li>Meta + p</li> | |
<li>Meta + f</li> | |
<li>H</li> | |
<li>E</li> | |
<li>L</li> | |
<li>O</li> | |
<li>1</li> | |
<li>2</li> | |
<li>3</li> | |
<li>4</li> | |
<li>5</li> | |
</ul> | |
` | |
}) | |
.Class({ | |
constructor: AppController | |
}) | |
; | |
return( AppController ); | |
// I control the App component. | |
function AppController() { | |
var vm = this; | |
// Expose the public events. | |
vm.handleKeyEvent = handleKeyEvent; | |
// --- | |
// PUBLIC METHODS. | |
// --- | |
// I handle the key event, logging the value. | |
function handleKeyEvent( event, value ) { | |
console.log( "Event (%s): %s", event.type, value ); | |
// Since all of the key combinations represent some sort of | |
// native browser behavior, we're just going to cancel all | |
// the behaviors to keep the demo simple. | |
event.preventDefault(); | |
} | |
} | |
} | |
); | |
</script> | |
</body> | |
</html> |
As you can see, the only logic I'm providing is the event binding in my template and the logging in my controller; all of the actual key-event implementation is provided by Angular 2's Browser platform, out-of-the-box. And, when we run this demo and try some of the key-combinations, we get the following output:

Awesome McAwesomeFace! The fact that Angular 2 supports key-combination event-binding out-of-the-box is like, totes powerful! I wish I had known about this earlier. Hopefully this helps shed some light for anyone else who may have completely missed this feature's existence in the Angular 2 guide.
Want to use code from this post? Check out the license.
Reader Comments
Hi ben thanks for the solution
but ctrl+s is not working and ctrl+enter too in your example as well..
@Shashank,
try keydown.meta.Enter (ctrl+enter)
and keydown.meta.s (ctrl+s)
This is still a great resource, because so far this isn't well documented in the oficial site.
@Elvin,
Awesome - thanks for the positive feedback :D
This is a great post. Thank you Ben for sharing this !