Using Element and Event Directives in AngularJS: Handling Events

HTML elements define events, which provide asynchronous notifications of user interactions. AngularJS defines a set of directives that specify custom behaviors when different types of event are triggered. I have listed the event directives in Table 11-3.

Tip AngularJS provides an optional module that supports simple touch events and gestures. See Chapter 23 for details.

The event handler directives can be used to evaluate an expression directly or to invoke a behavior in the controller. I won’t demonstrate all of these directives because they are essentially the same, but you can see the ng-click and two of the ng-mouse directives used in Listing 11-8.

Listing 11-8. Using Directives to Handle Events in the directives.html File

<!DOCTYPE html>

<html ng-app=”exampleApp”>

<head>

<title>Directives</title>

<script src=”angular.js”></script>

<link href=”bootstrap.css” rel=”stylesheet” />

<link href=”bootstrap-theme.css” rel=”stylesheet” />

<script>

angular.module(“exampleApp”, [])

.controller(“defaultCtrl”, function ($scope) {

$scope.todos = [

{ action: “Get groceries”, complete: false },

{ action: “Call plumber”, complete: false },

{ action: “Buy running shoes”, complete: true },

{ action: “Buy flowers”, complete: false },

{ action: “Call family”, complete: false }];

$scope.buttonNames = [“Red”, “Green”, “Blue”];

$scope.data = {

rowColor: “Blue”,

columnColor: “Green”

};

$scope.handleEvent = function (e) {

console.log(“Event type: ” + e.type);

$scope.data.columnColor = e.type == “mouseover” ? “Green” : “Blue”;

}

});

</script>

<style>

.Red { background-color: lightcoral; }

.Green { background-color: lightgreen; }

.Blue { background-color: lightblue; }

</style>

</head>

<body>

<div id=”todoPanel” class=”panel” ng-controller=”defaultCtrl”>

<h3 class=”panel-header”>To Do List</h3>

<div class=”well”>

<span ng-repeat=”button in buttonNames”>

<button class=”btn btn-info” ng-click=”data.rowColor = button”>

{{button}}

</button>

</span>

</div>

<table class=”table”>

<thead>

<tr><th>#</th><th>Action</th><th>Done</th></tr>

</thead>

<tr ng-repeat=”item in todos” ng-class=”data.rowColor”

ng-mouseenter=”handleEvent($event)”

ng-mouseleave=”handleEvent($event)”>

<td>{{$index + l}}</td>

<td>{{item.action}}</td>

<td ng-class=”data.columnColor”>{{item.complete}}</td>

</tr>

</table>

</div>

</body>

</html>

I have applied the ng-click directive to a set of button elements that I generate using the ng-repeat directive. The expression that I have specified will be evaluated when one of the buttons is clicked, directly updating a value in the data model, as follows:

<button class=”btn btn-info” ng-click=”data.rowColor = button”>

{{button}}

</button>

I assign a new value to the rowColor property defined by the controller, which is used by the ng-class directive I added to the tr elements for the table. The effect is that clicking one of the buttons changes the background color of the table rows.

Tip Unrelated to events, notice that when I use the ng-repeat directive to create a set of buttons in the example, I applied the directive to a span element, rather than to the button elements directly. Without the span element, there is no spacing between the buttons, which is not the effect I wanted to create.

If you are uncomfortable using inline expression—and many developers are—or if you need to perform complex logic, then you can define a behavior in the controller and invoke it from the event directive. This is what I did with the tr elements I create in the example:

<tr ng-repeat=”item in todos” ng-class=”data.rowColor”

ng-mouseenter=”handleEvent($event)” ng-mouseleave=”handleEvent($event)”>

I have applied the ng-mouseenter and ng-mouseleave directives to the tr elements, specifying that the handleEvent behavior should be invoked. This is similar to the traditional model of JavaScript event handling, and to access the Event object, I use the special $event variable, which all of the event directives define.

You must be careful when handling events in behaviors because there is a mismatch between the event names that AngularJS uses for the names of the directives and the value of the type property of the underlying events. In this example, I have added directives to handle the mouseenter and mouseleave events, but I receive different events in the behavior function:

$scope.handleEvent = function (e) {

console.log(“Event type: ” + e.type);

$scope.data.columnColor = e.type == “mouseover” ? “Green” : “Blue”;

}

The safest way to figure out which events you will receive in the behavior is to set up the function and use console.log to write the value of the type property to the console. In this way, I am able to tell that the mouseenter event will really be presented as mouseover and that the mouseleave event will be represented by mouseout. I check the type of the event I received and set the value of the data.columnColor model property to either Green or Blue. This value is used by the ng-class directive I applied to one of the td elements in the table, which has the effect of changing the color of the final table column when the mouse enters and leaves table rows.

Note This mismatch isn’t really the fault of AngularJS. The world of browser events, especially when it comes to dealing with the mouse or pointer, is a mess. AngularJS relies on jQuery, which takes care of some of the complexity, but it isn’t a perfect solution, and thorough testing is essential to make sure you are getting and handling the right events.

Creating a Custom Event Directive

I explain the different ways you can create custom directives in Chapter 15-17. It can be a complex process, and there are lots of features to understand and use. But for this chapter, I am going to show you how to create a simple directive that you can use in your own projects to handle events for which AngularJS doesn’t provide a built-in directive. I am going to give you only a cursory explanation of how it works, but it is such a common requirement that it is worth showing you the technique in the context of this chapter. In Listing 11-9, I have created a directive that handles the touchstart and touchend events, which are trigged by touch-enabled devices when the user taps and releases the screen.

Listing 11-9. Creating a Custom Event Directive in the directives.html File

<!DOCTYPE html>

<html ng-app=”exampleApp”>

<head>

<title>Directives</title>

<script src=”angular.js”></script>

<link href=”bootstrap.css” rel=”stylesheet” />

<link href=”bootstrap-theme.css” rel=”stylesheet” />

<script>

angular.module(“exampleApp”, [])

.controller(“defaultCtrl”, function ($scope, $location) {

$scope.message = “Tap Me!”;

}).directive(“tap”, function () {

return function (scope, elem, attrs) {

elem.on(“touchstart touchend”, function () {

scope.$apply(attrs[“tap”]);

});

}

});

</script>

</head>

<body>

<div id=”todoPanel” class=”panel” ng-controller=”defaultCtrl”>

<div class=”well” tap=”message = ‘Tapped!'”>

{{message}}

</div>

</div>

</body>

</html>

I create the directive using the Module.directive method that I introduced in Chapter 9. The directive is called tap, and it returns a factory function that, in turn, creates a worker function to process the element to which the directive has been applied. The arguments to the worker function are the scope in which the directive is operating (I describe scopes in Chapter 13), the jqLite or jQuery representation of the element to which the directive has been applied, and a collection of the attributes applied to the element.

I use the jqLite on method (which is derived from the jQuery method of the same name) to register a handler function for the touchstart and touchend events. My handler function calls the scope.$apply method to evaluate whatever expression has been defined as the value for the directive attribute, which I obtain from the attribute collection.

I describe jqLite in Chapter 15 and the scope $apply method in Chapter 13. I have applied the directive to the div element as I would any other directive, and my expression in this case modifies the message model property:

<div class=”well” tap=”message = ‘Tapped!’“>

You will need to enable touch event emulation in Google Chrome to test this example (or use a device or emulator that supports touch) because the touchstart and touchend events are not triggered on mouse-only platforms. When you tap the div element, the contents will change, as illustrated in Figure 11-8.

Source: Freeman Adam (2014), Pro AngularJS (Expert’s Voice in Web Development), Apress; 1st ed. edition.

Leave a Reply

Your email address will not be published. Required fields are marked *