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 !