Consuming Event Properties From Within The View In AngularJS
Yesterday, I posited that passing / injecting the $element and $event objects into a Controller should be considered an anti-pattern in AngularJS. I believe that doing so, while it has its uses, breaks the separation of concerns between the view-model and the DOM (Document Object Model). That said, I did want to point out that you can consume $event properties from within in the View, which may provide the necessary information that you are looking for without having to violate the separation of concerns.
Run this demo in my JavaScript Demos project on GitHub.
Before we look at the code, let's just get on the same page about how the $event object is exposed. When one of the core event directives (ex, ngClick, ngMousedown, ngKeypress) goes to evaluate your scope expression, it provides the $event object as one of the "locals". When AngularJS locates the values used in the expression, it will look in both the current scope tree as well as the locals which means that it will be able to find contextual references (such as an ngRepeat iterator) as well as the $event object, which is being provided for in the locals.
Now, AngularJS doesn't necessarily care how you use the located values - it just makes them available. Which means, we don't have to pass the values into a method, wholesale; rather, we can pass properties of the located values into the method calls within the expression.
To see this in action, I've put together a small demo that logs click events and key events in a contentEditable Div. Notice that I am not passing the $event object into the Controller. Rather, I am passing $event properties into the controller:
<!doctype html>
<html ng-app="Demo">
<head>
<meta charset="utf-8" />
<title>
Consuming Event Properties From Within The View In AngularJS
</title>
<link rel="stylesheet" type="text/css" href="./demo.css"></link>
</head>
<body ng-controller="AppController as vm">
<h1>
Consuming Event Properties From Within The View In AngularJS
</h1>
<!--
The reason that you can pass the $event object from the view into the methods
is because it is being exposed as a "locals" override by the directives (ex,
ngClick, ngKeypress) when evaluating your directive expression (pseudo code):
`scope.$eval( attributes.ngClick, { $event: event } )`
However, that means that you can also consume the event object properties
directly as part of the same method invocations (as opposed to passing in the
entire $event object).
-->
<div
ng-click="vm.logClick( $event.pageX, $event.pageY )"
ng-keypress="vm.logKey( $event.which, $event.metaKey )"
contenteditable="true"
class="logger">
<!-- Events will be logged to console. -->
</div>
<!-- Load scripts. -->
<script type="text/javascript" src="../../vendor/angularjs/angular-1.4.7.min.js"></script>
<script type="text/javascript">
// I control the root of the application.
angular.module( "Demo", [] ).controller(
"AppController",
function AppController( $scope ) {
var vm = this;
// Expose public methods.
vm.logClick = logClick;
vm.logKey = logKey;
// ---
// PUBLIC METHODS.
// ---
// I log the given click coordinates.
function logClick( x, y ) {
console.log( "Click:", x, y );
}
// I log the given key characters.
function logKey( keyCode, isMetaKey ) {
console.log( "Key:", String.fromCharCode( keyCode ), keyCode, isMetaKey );
}
}
);
</script>
</body>
</html>
As you can see, I am consuming the $event properties - pageX, pageY, which, and metaKey - in the View, extracting them from the $event object and passing them into the Controller. In this way, we can consume event data without having the Controller know about the DOM and how it manages events.
When I run this page and interact with the contentEditable Div, we get the following output:
Now, I will frankly admit, I doubt this will actually get you anywhere. It's hard for me to think of a scenario in which I could consume event properties without also knowing more about the DOM and / or being able to cancel events. The pageX and pageY coordinates are most likely useless without being able to translate them from a global to a local coordinate set, which the Controller cannot due (since it has no concept of DOM-based coordinates). And, listening for key events is almost always coupled with selectively canceling an event, which the Controller cannot due since the key-event is a DOM-related concern.
So, I guess, really, this is an academic post more so than a practical one. But, if you can find a way to cleanly consume those event properties, at least this will allow you to get them into the Controller without blurring the lines of responsibility between the Controller (concerned with the view-model) and the View / Directives (concerned with the DOM and DOM-events).
Want to use code from this post? Check out the license.
Reader Comments