Controllers and Directives

In AngularJS, you have your Views, which present data to the user; you have your Controllers, which manage the $scope (i.e. view model) and expose behavior to the View; and, you have your Directives, which link user interactions to $scope behaviors. But then you also have a special kind of Controller – a Directive Controller. The Directive Controller is defined within the context of one directive; but, it can be injected into other directives as a means to facilitate inter-directive communication.

When you start using AngularJS, you definitely need to change the way you think about application architecture. AngularJS enforces a very strict separation of responsibilities, making sure that your DOM updates are abstracted away from your model updates. Directives act as the layer that keeps these two aspects loosely coupled. One hand, directives translate data into user interfaces; and, on the other hand, directives translate user interactions back into $scope behaviors.

So, where do Directive Controllers fit into this model? Well, I’m still not 100% sure. So far, I’ve only just begun to really play around with directive controllers and how inter-directive communication works. As such, I’m not sure I can even codify underlying rules. Here’s what I think as of this writing:

  • Link functions capture user behavior.
  • Link functions execute $scope.$apply() calls.
  • Directive controllers can assume an active $digest, given the rule above.
  • Directive controllers can alter the DOM, but should defer to the Link functions for user interactions.

Those are just some raw thoughts, so it may not make too much sense. That said, between Views, Controllers, Directives, and Directive Controllers, how do you figure out what functionality goes where?

As a rule of thumb, I’ve been trying to build my views as if I didn’t have any JavaScript available – as if I was simply rendering data. Then, I use directives to add the JavaScript-driven features back in. To demonstrate, I’ve created a Master-Slave, drag-drop project. In it, I have a master canvas and collection of slave “handles.” The user can click on one of the slave handles and move it around. The master canvas then forces all slave handles to move in unison.

This requires a good bit of JavaScript interaction. But, how did I build the page? I built it as if I had no JavaScript; I built it as if I simply had a list of items:

<!doctype html>

<html ng-app=”Demo”>

<head>

<meta charset=”utf-8″ />

<title>Using Controllers In Directives In AngularJS</title>

<link rel=”stylesheet” type=”text/css” href=”app/css/demo.css”></link>

</head>

<body>

<h1>

Using Controllers In Directives In AngularJS

</h1>

<!– BEGIN: Master Canvas. –>

<div

ng-controller=”MasterController”

bn-master

class=”master”>

<!– BEGIN: Slave Handles. –>

<ol class=”handles”>

<li

ng-repeat=”slave in slaves”

ng-controller=”SlaveController”

bn-slave

class=”slave”

ng-style=”{ left: ( slave.x + ‘px’ ), top: ( slave.y + ‘px’ ) }”>

{{ slave.id }}

</li>

</ol>

<!– END: Slave Handles. –>

<!– BEGIN: Slave Leaderboard. –>

<ol class=”leaderboard”>

<li ng-repeat=”slave in slaves”>

<div class=”label”>

{{ slave.id }}

</div>

<div class=”position”>

<span class=”coordinate”>{{ slave.x }}px</span>

<span class=”coordinate”>{{ slave.y }}px</span>

</div>

</li>

</ol>

<!– END: Slave Leaderboard. –>

</div>

<!– END: Master Canvas. –>

<!– Load jQuery and AngularJS from the CDN. –>

<script

type=”text/javascript”

src=”//code.jquery.com/jquery-1.9.0.min.js”>

</script>

<script

type=”text/javascript”

src=”//ajax.googleapis.com/ajax/libs/angularjs/1.0.2/angular.min.js”>

</script>

<!– Load the app module and its classes. –>

<script type=”text/javascript” src=”app/main.js”></script>

<script type=”text/javascript” src=”app/controllers/master-controller.js”></script>

<script type=”text/javascript” src=”app/controllers/slave-controller.js”></script>

<script type=”text/javascript” src=”app/directives/master.js”></script>

<script type=”text/javascript” src=”app/directives/slave.js”></script>

</body>

</html>

As you can see, this HTML renders an ordered list. It’s the directives – bnMaster and bnSlave – that add all of the user-interaction JavaScript. There’s a decent amount of code that goes into this demo, so I’ll leave that on the GitHub repo rather than cover it here.

That said, there is one underlying concept that I want to drive home: as you add user interaction behavior, minimize the number of $digests that run. While AngularJS allows you to render a view based on $scope data, you should try to use direct DOM manipulation as much as possible while in the context of a directive. Defer updating the $scope and invoking $scope.$apply() as long as possible. This will keep your page feeing much more responsive.

If you look at the code above, you can see that the position of each slave handle is defined by an ngStyle directive; this maps the $scope data onto CSS properties. However, if you look at the video (or use try the code), you’ll notice that the X/Y coordinates in the “leaderboard” don’t update until you actually release the mouse. This is because I am deferring $scope updates (and subsequent $digests) until the movement has concluded.

Directive without a Controller

Directives provide several different ways to render HTML, collect data, and perform additional tasks. In situations where a directive is performing a lot of DOM manipulation, using the link function makes sense.  Here’s a simple example of the link function in action:

(function() {

var app = angular.module(‘directivesModule’);

app.directive(‘domDirective’, function () {

return {

restrict: ‘A’,

link: function ($scope, element, attrs) {

element.on(‘click’, function () {

element.html(‘You clicked me!’);

});

element.on(‘mouseenter’, function () {

element.css(‘background-color’, ‘yellow’);

});

element.on(‘mouseleave’, function () {

element.css(‘background-color’, ‘white’);

});

}

};

});

}());

Adding a controller into this directive doesn’t make much sense given that the goal is to handle events and manipulate the DOM. Although it would be possible to accomplish the same task using a view in the directive (along with built-in AngularJS directives such as ng-click) and controller there’s really no reason to add a controller into this directive if DOM manipulation is the overall end goal.

In cases where you’re manipulating the DOM, integrating data into the generated HTML, handling events, and more, adding a controller can minimize the amount of code you write and simplify the overall process in some cases. To make this more clear, let’s look at an example of a directive then renders a list of items and provides a button that can be used to add items to the list.

Share this post
[social_warfare]
AngularJS Scopes
Processing DOM Elements

Get industry recognized certification – Contact us

keyboard_arrow_up