AngularJS Made Easy, by Example – Part 3; Views & Directives

The third part of a series on rapidly learning the AngularJS JavaScript framework. Today I illustrate the use of Views and Directives within an AngularJS application

I am presented with something of a dilemma; there is simply no way to conveniently sequence this tutorial series when the different pieces are so closely interrelated. I could easily have made today’s topic about Controllers, and then followed up with Views and Directives next time, but to me Controllers should could later.

Views are essentially simple beasts; regular HTML files spiced with a few AngularJS keywords and bound to data values of your choosing. But they can very easily spiral out of control, and that’s where your coding instinct comes in; if it’s becoming an absolute nightmare to structure and your brain is getting foggy then that’s a code smell; the View is probably doing too much. This equally applies to Directives, and due to the one-to-one nature of Views to Controllers the less a View has to do the less its Controller has to do, and fat Controllers are to be avoided at all cost.

With no further ado, here’s one of my Views:

<h2 class="pagetitle" data-ng-hide="teachingClass.subjectname === undefined && teachingClass.name === undefined">{{ teachingClass.subjectname }} - {{ teachingClass.name }}</h2>
    <a id="ancBack" class="back" href="/teacher/teachingclasses/">Back</a>
    <div class="plaincontainer">
         Students
         <table>
              <thead>
                   <tr>
                        <th style="width: 35%;">
                             Name
                        </th>
                        <th style="width: 45%;">
                             Email
                        </th>
                        <th style="width: 20%;">
                             Appraised / Sessions
                        </th>
                   </tr>
              </thead>
              <tbody>
                   <tr data-ng-repeat="student in teachingClassStudents">
                        <td>
                             <a class="menubutton orange withintable" href="/teacher/teachingclass/{{ teachingClass.id }}/student/{{ student.studentid }}">{{ student.studentname }}</a>
                        </td>
                        <td>
                             {{ student.studentemail }}
                        </td>
                        <td>
                             <span data-studentprogresscolourcoding="" data-teacherappraisedtotal="student.teacherappraisedtotal" data-totalsessions="student.totalsessions" data-lowerthreshold="student.lowerthreshold" data-upperthreshold="student.upperthreshold"> / {{ student.studentprogresstotalsessions }}</span>
                        </td>
                   </tr>
              </tbody>
         </table>
    </div>

Mostly regular HTML, with a few extras. Anything inside braces, such as {{ teachingClass.subjectname }} is a reference to a data value that is setup within the Controller. The data-ng-hide attribute in the top H2 tag is an inbuilt AngularJS Directive to hide the contents of the element depending on whether the statement that follows it evaluates to true or false. In this case I’m using it to hide the header until the selected teaching class details have finished loading, as otherwise I end up with a dash symbol (“-”) on its own, hanging in mid-air for a few seconds. There is a horde of AngularJS Directives built into AngularJS, descriptions of each one available on the AngularJS website. If you’re trying to do something a little tricky in a View I recommend you first give the list of inbuilt Directives a read, as the functionality you’re trying to achieve may well already exist out-of-the-box.

One on AngularJS’s key features is its templating capabilities, illustrated in the above View inside the TBODY tag. The data-ng-repeat attribute will look in an object array I will create in my Controller called “teachingClassStudents”, which contains each of the students in this teaching class. The data-ng-repeat attribute will iterate through each value in the array, in this case creating a local instance of them called “student”, allowing access into the innards of the data object and thus the ability to build a few TD tags with which to populate my table row. In particular I draw your attention to the first TD tag, which plants a couple of bound data values into an anchor tag’s href attribute (look back at Part 2’s description of routings to see how these get mapped to other Views).

Eventually we come to that chunky-looking SPAN tag, housed inside the last of the TD tags. The first attribute, data-studentprogresscolourcoding, isn’t a Directive built into AngularJS; it’s one of my own creations. The attributes that follow this allow me to pass data into the Directive from the each “student” object I iterate through as the table rows are built. The Directive code itself is contained in a JavaScript file, looking something like this:

app.directive('studentprogresscolourcoding', function ()
{
	return {
		restrict: 'A',
		scope:
		{
			teacherappraisedtotal: '=',
			totalsessions: '=',
			lowerthreshold: '=',
			upperthreshold: '='
		},
		link: function (scope, element, attrs)
		{
			scope.$watchCollection('[teacherappraisedtotal, totalsessions, lowerthreshold, upperthreshold]', function ()
			{
				var backgroundColourClass = GetColourCodingCssClass(scope.teacherappraisedtotal, scope.totalsessions, scope.lowerthreshold, scope.upperthreshold);
				element.removeClass('appraisedstatus red green yellow');
				element.addClass(backgroundColourClass);
				element.html(scope.teacherappraisedtotal);
			});
		}
	}


	function GetColourCodingCssClass(prmAppraisedCount, prmTotalSessions, prmLowerThreshold, prmUpperThreshold)
	{
		var percentageTicked = (prmAppraisedCount / prmTotalSessions) * 100;
		if (percentageTicked <= prmLowerThreshold)
		{
			return 'appraisedstatus red';
		}
		else if (percentageTicked >= prmUpperThreshold)
		{
			return 'appraisedstatus green';
		}
		else
		{
			return 'appraisedstatus yellow';
		}
	}
});

The main purpose of custom Directives is to encapsulate any DOM manipulation that your application requires; it is bad practice to make any DOM alterations inside Controllers – a custom Directive is the place for it. In my above code, whenever one of the values I pass into the Directive changes (the values are in the fields are “teacherappraisedtotal”, “totalsessions”, “studentsubjectlowerthreshold” or “studentsubjectupperthreshold”) then I recalculate a percentage value that indicates how well the student is doing overall against their assessments, and I attach a CSS class to the SPAN tag to change the background colour accordingly.

The key thing here is scope.$watchCollection, which makes sure that AngularJS runs the anonymous function whenever any one of the values change. If I didn’t wrap the commands in scope.$watchCollection then they would only be run once when the Directive first instantiates itself in each row, but as these values might change I need to “watch” them so my SPAN colouring logic can be automatically triggered after the table has been built.

In the next article in this series I shall describe my implementation of Controllers; the thing that coordinates all the data to fuel these Views and Directives.