Programming | Computer Science | Software and Technology

Saturday, September 5, 2015

In my first article we briefly discussed about AngularJS and the importance of such a framework to build dynamic web applications. Before continuing with the second part of it, I decided to write an article about one of its beautiful concepts called "Directives", since it is better to understand the importance of it with a real world usage rather than a detailed explanation. I will be demonstrating the importance of Directives by building our own custom directive step by step.

What is a directive?

The word 'directive' was not that familiar to us before AngularJS was introduced. If you have used HTML before, (Ok, pardon me! I know you won't be reading this article if you haven't used HTML before! :)) you know what are elements, attributes, classes, comments etc.

Elements
  • <html>
  • <body>
  • <script>
  • <div>
You know many of them.

  • href
  • hidden
  • id
  • label
Sounds familiar right? Therefore I assume you already know about classes and html comments too. 

Even though HTML had those rich set of elements, attributes etc., the control the developers had over those was minimal. Even though the latest browsers and the latest html specification support custom elements, it only fulfills some basic needs and also not that easy to implement and distribute them.

This is where the AngularJS directives come in handy. According to their official statement,
At a high level, directives are markers on a DOM element (such as an attribute, element name, comment or CSS class) that tell AngularJS's HTML compiler ($compile) to attach a specified behavior to that DOM element (e.g. via event listeners), or even to transform the DOM element and its children.
Okay, lets make it simple

So AngularJS directives are a set of properties which we can assign to HTML elements (or the entire element itself can also be a directive) so that when AngularJS compiles the HTML template, it is capable of giving those elements the preferred behavior we ask them to have.

For example lets say we want to have an DOM element which displays the current time. We want this element to be used in multiple places in our application too. This is a scenario where directives are useful. We can do either of below to achieve the result,

Create it as a custom element
<current-time></current-time>

Take an existing element and attach the behavior as an attribute

<div current-time class="my-time-widget">
    Current time :
</div>

We can use this tag or attribute anywhere inside our html markup to display the current time. It makes our lives easier and it is also dead simple to distribute this among various applications too.

There are hundreds of directives introduced by AngularJS like ng-app, ng-model, ng-controller, ng-bind etc.

How to implement our own directive (custom directives)

At the end of this article, we are going to build a simple AngularJS directive which displays a line-chart according to the data provided. It will look like this. Ignore all the angular related stuff and just pay attention on the <line-plot> element for now.

index.html

<!DOCTYPE html>
<html ng-app="graphPlotterDemoApp">
<head lang="en">
</head>
<body ng-controller="graphPlotterDemoCtrl">

<!-- This is where we are going to put our custom directive -->
<line-plot graph-plots=graphPlots></line-plot>

<!-- Here we will import the required javascript -->
</body>
</html>

We all know that <line-plot> is not a standard HTML tag. Therefore that is what we are going to create!

Result



Prerequisites 

The main purpose of a custom directive is to make components reusable. Therefore it makes sense to make use of existing javascript or jquery based components and convert them into AngularJS custom directives. For my directive I am using this great javascript plotting library called Plotly. According to their description,
plotly.js is Plotly's client-side, interactive JavaScript graphing library, built on top of D3.js and stackgl. plotly.js supports all SVG-compatible browsers.
If you are interested you can go through their documentation on how to build rich industry ready graphs. It is really worth giving a try. But for the purpose of this article, all you have to need is download their library javascript file.

You can download it HERE (It contains the full distribution. We will only need
  • jquery-latest.js
  • plotly.min.js
  • d3.v3.min.js
Files)

The other requirement is to download angularjs.min.js.

Okay! That's all we need to proceed.

Step 0 - Creating the directory structure

Open you favorite html/js editor (I am using Sublime Text) and create a folder structure like below.


Put the 3 javascript files you downloaded from Plotly inside the AngularJSGraphPlotter/public/js/libs folder. (You don't even need to open those files. Just keep them there.)

Create 2 javascript files called,
  • app.js - This will contain the angular module for our demo application. (It is your application.)
  • graph-plotter.js - This is where we implement our own custom directive.
Now it is time to code. I know you all are excited!! :)

Step 1 - Create the index.html

In the above folder structure, you can see that the index.html file is located at the root. Just open that file and write whatever the preferred basic html skeleton you prefer. For the moment all you have to do is import the javascripts in the correct order. So, it should be like this.

index.html

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>Plotly Graph Plotter Directive for AngularJS - Demo</title>
    <meta name="description" content="AngularJS custom directive - graph plotter">
    <meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
    <!-- This is where we are going to put our custom directive, later! -->
    <!-- Import the required javascripts like below. The order is important -->
    <script src="public/js/libs/jquery-latest.js"></script>
    <script src="public/js/libs/angular.min.js"></script>
    <script src="public/js/libs/d3.v3.min.js"></script>
    <script src="public/js/libs/plotly.min.js"></script>
    <script src="public/js/graph-plotter.js"></script>
    <script src="public/js/app.js"></script>
</body>
</html>

Note on the order of importing javascript files (Skip if you understand)

Since all the 3 Plotly libraries require jquery, it should be loaded before everything. We have imported the angularjs after that. We know that our application module (app.js) makes use of our custom directive. Therefore graph-plotter.js should be imported before app.js. (These explanations will waste the valuable time of experienced programmers. If you feel like you know what you are doing before I explain it, feel free to fast-forward)

Now lets get into the real business.

Step 2 - Understanding the very basic of Plotly - Line Charts 

Before moving on, I will explain only  the relevant details you have to know about plotting a simple Line Graph using Plotly library.

All you need to plot a basic line chart is the set of the x coordinates along with the corresponding y coordinates. The same applies to plotting a graph using Plotly too!!

You can define the data points as an javascript object like below. This is the object we need to pass to the Plotly function (we'll do it next), to plot the graph.

var myLineGraph = {
x: [1, 2, 3, 4],
y: [10, 15, 13, 17],
type: 'scatter'
};

// you can add multiple plots to the same graph. That is why this is an array.
var data = [myLineGraph];    

// Plot the graph(s) in the myDiv HTML element 
Plotly.newPlot('myDiv', data);

The above code is self-explaining. The graph object contains the x and y data points as arrays. Simpy saying, the above will plot the points (1, 10), (2,  15), (3, 13) and (4, 17) on a graph. By the attribute type we tell Plotly that we need to make it a scatter graph. Which simply means we need to make it look like a connected graph of those points. (For further options and graph types you can refer to their official documentation on line charts. For the purpose of this article, I will keep it simple like this.)
Actually what we are going to do is, define and manipulate the data required to plot the graph inside our application module (app.js) and use the custom attribute to display them in a line graph. By separating the data and the logic of displaying like this, we preserve the re-usability and extensibility. It also makes our code readable. Those are the expected outcomes of an AngularJS Directive.
Step 3 - Implementing our application module

Now we are going to implement our application.  This can be a sales system, banking application, dashboard of a blog, POS system etc. For the sake of simplicity we will make our application a Line Graphs - Demo Application. (Because all we are going to do in this application is demonstrating our directive :P)

As I mentioned earlier, we are writing the application module inside the app.js file. So we will go ahead and open it. First think of a good name to give it. Because every angular module should have a name! So what else is better than a beautiful name like 'graphPlotterDemoApp'. So lets create the module. Its simple,

var graphPlotterDemoApp = angular.module('graphPlotterDemoApp', []);

This tells angular, that we are have created a module and its name is graphPlotterDemoApp. Also, currently it does not have any dependencies (but we are going to have one soon!:).

For now, everything looks great! But we do not have any logic to control our application. This is where we have to attach a controller to our module. A controller is where we define our application data and relevant simple logic (note that we do not write complicated logic inside controllers. We will have a separate discussion on this later) to manipulate the data if needed.

This is where we are going to keep our data so that we can pass them to our directive to draw the graph. So lets go ahead and create a controller.

graphPlotterDemoApp.controller('graphPlotterDemoCtrl', ['$scope', function ($scope) {
    // The controller logic goes here
}]);

We attach controllers to modules. Therefore we use the variable graphPlotterDemoApp (the same variable we assigned to our module) attach our controller to it. Same as a module we have to give it a name too, because the first parameter controller accepts is the name. In our case I have named it as the graphPlotterDemoCtrl (It is a convention to end the controllers' name with 'Controller'. In our case since it is too long, I have used 'Ctrl').

The second parameter required by the controller  is either a anonymous function or an array of dependencies along with the function. In my case I have used the second approach. We need the $scope object to make the connection between the view (our html template) and the controller, so it becomes a dependency for our controller. So inside the anonymous function we can explicitly create the link using this object. Don't worry! We will discuss about all of these in a separate article about controllers.

Now it is high time we define our data inside this controller and make the connection between this data and the view. As we learned earlier, in order to plot the graph we need to have a graph object. So lets create one!

var trace1 = {
    x: [1, 2, 3, 4],
    y: [10, 15, 13, 17],
    type: 'scatter'
};

Yeah you already know what that means. So all we have to do now is attach this object to the $scope object so that we can use this object in the view. Making the attachment is really simple.

$scope.graphPlots = [trace1];

What we have done is attaching a variable named graphPlots to the $scope and assigning our graph object (trace1) to it as an array.
Yeah I know what you're thinking right now! Why didn't I just do,

$scope.graphPlots = trace1;

It is quite simple and easy to read.

Actually the reason for making the graphPlots an array is making it possible to draw multiple plots in the same graph. If you looked at the image I have shown at the beginning, it has multiple plots in the same graph. So what we can do is define multiple plot data like below,

var trace1 = {
    x: [1, 2, 3, 4],
    y: [10, 15, 13, 17],
    type: 'scatter'
};

var trace2 = {
    x: [1, 2, 3, 4],
    y: [16, 5, 11, 9],
    type: 'scatter'
};

var trace3 = {
    x: [1, 2, 3, 4],
    y: [23, 4, 2, 6],
    type: 'scatter'
};

var trace4 = {
    x: [1, 2, 3, 4],
    y: [9, 11, 3, 12],
    type: 'scatter'
};

and attach them to the graphPlots variable as an array, like below,

$scope.graphPlots = [trace1, trace2, trace3, trace4];

So that we can plot all the four plots in the same graph. That is why I made graphPlots an array! I hope this makes sense.

Okay, now we have to inject the graphPlotter dependency to our application module so that we can use the directive inside our application. At the moment we haven't created this module. We will create it in the next step. But for the completeness we will just add it. We do it by changing our module registration into this,

var graphPlotterDemoApp = angular.module('graphPlotterDemoApp', ['graphPlotter']);

Now we have completed our application module! Great :D Finally it will look like below,

/**
 * Created by ArunaTebel on 9/4/2015.
 */

var graphPlotterDemoApp = angular.module('graphPlotterDemoApp', ['graphPlotter']);

graphPlotterDemoApp.controller('graphPlotterDemoCtrl', ['$scope', function ($scope) {
    var trace1 = {
        x: [1, 2, 3, 4],
        y: [10, 15, 13, 17],
        type: 'scatter'
    };

    var trace2 = {
        x: [1, 2, 3, 4],
        y: [16, 5, 11, 9],
        type: 'scatter'
    };

    var trace3 = {
        x: [1, 2, 3, 4],
        y: [23, 4, 2, 6],
        type: 'scatter'
    };

    var trace4 = {
        x: [1, 2, 3, 4],
        y: [9, 11, 3, 12],
        type: 'scatter'
    };

    // Attach the four plots as single array to the $scope.
    $scope.graphPlots = [trace1, trace2, trace3, trace4];
}]);

Now here comes the serious part. Just kidding, it is not that much.

Step 4 - Creating the directive

As I mentioned at the beginning of this article, what the developer who uses this directive has to do is just put the html element,

<line-plot></line-plot> 

where he wants to render the graph. (We need to attach the graphPlots object to this element in order to tell the directive that it contains the relevant data to make the graph. We will see how to do it)

We know that we are going to implement the directive inside our graph-plotter.js file. So go ahead and open it.

We cannot create a directive without a module. Same as the controllers we have to attach directives to modules too. Therefore we will first create our module. It is same as we did before,

var graphPlotter = angular.module('graphPlotter', []);

Now it is the fun part. We have to attach the directive to this module and also give it a name. In this case, the name should be the same as the element name; but normalized. By normalizing what we mean is this,

if the element's name is line-plot its normalized name is linePlot. All we have to do is omit the '-' and make the first letter of the second word capitalized. This is required by angular to make it work. So we have to name our directive as linePlot.

Attaching the directive to the module is same as attaching a controller,

graphPlotter.directive('linePlot', [function () {
    // Directive logic goes here
}]);

Here we have used the directive factory method to attach the linePlot directive to our graphPlotter module. Since there are no dependencies, the array only contains the anonymous function. Now we will look at the way of telling the AngularJS compiler the behavior of our directive.

When the compiler goes through the template, it will come across our nice little element <line-plot>. So it will look up in the registered directives and will get to know that we have registered it as a directive in out module graphPlotter. Great! But now the compiler needs to know how it should render/manipulate/decorate this element so that it will do/show something useful for us. This is where the link option of an directive comes in to play.

This is how AngularJS describes the link option of a directive.
Directives that want to modify the DOM typically use the link option. link takes a function with the following signature,function link(scope, element, attrs) { ... } where:
scope is an Angular scope object.
element is the jqLite-wrapped element that this directive matches.
attrs is a hash object with key-value pairs of normalized attribute names and their corresponding attribute values.
Don't worry it does not mean anything for you. We will sort it our soon! Since we are going to modify the DOM, it is obvious that we need to use the link option. As they have stated, link option will be a function which takes scope, element and attrs as parameters. Here, the scope object is nothing else but the $scope object we attached our graphPlots variable inside the controller of our application module. To clarify a little bit it is like this,
  • In our application module controller (graphPlotterDemoCtrl), we used the $scope object and attached the graphPlots variable (which contains the data for the 4 plots) to it.
  • So in the template we are attaching it to our element like this,
    • <line-plot graph-plots=graphPlots></line-plot>
      
  • Now the directive also have the ability to use it (graphPlots) inside it's logic.
  • But in order for the directive to access it, it needs to obtain the entire scope object of the controller which is linked to this element. i.e : the $scope object of the graphPlotterDemoCtrl controller.
Therefore the scope parameter of the link function will be the $scope object used by the controller which owns this element. I hope this makes sense to you. It is going to be much clearer after the below section; I guarantee!

The next parameter of the link  function is element. It is just the DOM element which we have made,

<line-plot graph-plots=graphPlots></line-plot>

The next parameter attrs contains all the attributes, (ids, classes, type, src, href etc) of the element.

Now we know about the link function and are ready to implement it!

We implement the link function inside our directive's anonymous function. (Obviously!) You can give it a name you prefer. I have named mine the linkFunc.

function linkFunc(scope, element, attrs) {
    // Linking logic goes here
}

Now even though we have the full scope object we only need to know about the graphPlots object. (Because sometimes, when the graphPlotterDemoCtrl grows bigger, more and more variables and objects will be attached to it. But for the directive to work, we only need the graphPlots object.)

So in order to extract out the the graphPlots variable from the scope we can register the listener called $watch on this scope. It will be watching for any changes of the scope object and will execute the callback function we give it. It can be done like this,

function linkFunc(scope, element, attrs) {
    /**
    registering the $watch linstener, and asking it to watch the graphPlots variable attached to 
    the scope. Also execute the given anonymous function 
    whenever a change happens in the graphPlots.
    **/
    scope.$watch('graphPlots', function (plots) {
        // Here we will plot the actual graph and attach it to the element soon!
    });
}

Hope it is clear too!

Now we are almost there. What we have left to do is read the data from the graphPlots, create the graph and render it in the HTML element.

Now it is the time to make use of Plotly!

We know that graphPlots is an array. That is great because Plotly also wants an array of plots to do its work! So it is just a matter of passing it to the newPlot function of Plotly along with the element.
Plotly.newPlot(element[0], plots);

So far so good! Now our linkFunc function is complete! It will look like this,

function linkFunc(scope, element, attrs) {
    scope.$watch('graphPlots', function (plots) {
        Plotly.newPlot(element[0], plots);
    });
}

Now it is the final thing to do! As I mentioned earlier, link  is an option of a directive. Therefore we have to tell the compiler that the above function is our linking option. It is so easy. All we have to do is return an object containing the link option (which is assigned the above function). So the complete directive will look like this,

/**
 * Created by ArunaTebel on 9/4/2015.
 */

var graphPlotter = angular.module('graphPlotter', []);

graphPlotter.directive('linePlot', [function () {
    function linkFunc(scope, element, attrs) {
        scope.$watch('graphPlots', function (plots) {
            Plotly.newPlot(element[0], plots);
        });
    }

    /** Return the object having the linkFunc as the link option. So that the compiler will use 
    the linkFunc to make the actual linking.**/
    return {
        link: linkFunc
    };
}]);

So that is our tiny directive (which is only 13 lines of code) which does all we need. Now we can use this beauty inside our template. Attach our controller to the <body> so that the element can use the graphPlots variable,

<body ng-controller="graphPlotterDemoCtrl">

Then, inside the body, put our element like this!

<!-- This is where we are going to put our custom directive -->
<line-plot graph-plots=graphPlots></line-plot>

Now everything should be perfect, and you should be able to open the index.html in the browser and see it working!

To wrap things up here is what we did,
  • Created the HTML markup to render our graph later
  • Got to know about how Plotly works.
  • Created our application module, which contains necessary data to plot the graph.
  • Created the module and attached our directive to it. Directive access the scope object of our application module via the link function. Then it renders the graph using the graphPlots array attached to that scope.
So, finally we have implemented a custom AngularJS directive (as an element) which can be used to render a graph given the relevant data. Here is the complete code for the index.html, app.js and the graph-plotter.js.

index.html

<!DOCTYPE html>
<html ng-app="graphPlotterDemoApp">
<head lang="en">
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>Plotly Graph Plotter Directive for AngularJS - Demo</title>
    <meta name="description" content="">
    <meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body ng-controller="graphPlotterDemoCtrl">

<!-- This is where we are going to put our custom directive -->
<line-plot graph-plots=graphPlots></line-plot>

<script src="public/js/libs/jquery-latest.js"></script>
<script src="public/js/libs/angular.min.js"></script>
<script src="public/js/libs/d3.v3.min.js"></script>
<script src="public/js/libs/plotly.min.js"></script>
<script src="public/js/graph-plotter.js"></script>
<script src="public/js/app.js"></script>
</body>
</html>

app.js

/**
 * Created by ArunaTebel on 9/4/2015.
 */

var graphPlotterDemoApp = angular.module('graphPlotterDemoApp', ['graphPlotter']);

graphPlotterDemoApp.controller('graphPlotterDemoCtrl', ['$scope', function ($scope) {

    var trace1 = {
        x: [1, 2, 3, 4],
        y: [10, 15, 13, 17],
        type: 'scatter'
    };

    var trace2 = {
        x: [1, 2, 3, 4],
        y: [16, 5, 11, 9],
        type: 'scatter'
    };

    var trace3 = {
        x: [1, 2, 3, 4],
        y: [23, 4, 2, 6],
        type: 'scatter'
    };

    var trace4 = {
        x: [1, 2, 3, 4],
        y: [9, 11, 3, 12],
        type: 'scatter'
    };

    $scope.graphPlots = [trace1, trace2, trace3, trace4];
}]);

graph-plotter.js

/**
 * Created by ArunaTebel on 9/4/2015.
 */

var graphPlotter = angular.module('graphPlotter', []);

graphPlotter.directive('linePlot', [function () {
    function linkFunc(scope, element, attrs) {
        scope.$watch('graphPlots', function (plots) {
            Plotly.newPlot(element[0], plots);
        });
    }

    return {
        link: linkFunc
    };
}]);

If you have got any errors or  doubts feel free to ask for help in the comment area! :)

Wednesday, August 26, 2015

AngularJS is really a Super-heroic (not just because they say so) framework when it comes to modern web application development. With the introduction of AngularJS, there was no longer a place for most of the loopholes and smelly codes in client side programming. It helped the web developers a lot to manage, reuse and extend their code by providing the the well reputed MVC architecture and a blueprint to build their applications upon. So lets make a quick peep into this awesomeness!

Why so dynamic?

Nowadays, when we browse through the internet, we come across thousands of types of those fancy features in web apps. We seldom find pure HTML-only websites (almost never web apps). This is due to the extensive list of features provided by Javascript, which took the web application domain into a whole new level during the past two decades. A few years back,
  • After submitting a form, we had to wait for a considerable amount of time until the page completely reloads, just to know that we have done a mistake typing our email address. Great! Now go on and fill out it again to find what will go wrong this time. 
  • We had all the trouble in the world just to watch an album of images one by one. Yes, we had next, previous buttons! But to view the next image, we had to reload the entire web page with hundreds of images on it. (If you are lucky enough, you will land on the same scroll position of the page. Otherwise you have to scroll down too)
  • After doing a small change to your email preferences, we had to reload the entire page to see the change in action.
  • We did not have the comfort of experiencing the auto-fill, suggestions features in text input elements.
Since in '95 Brendan Eich introduced Javascript, hundreds of millions of websites and web apps have been using it to provide a rich dynamic experience to their users. Great libraries like jQuery, jQuery UI, YUI, D3.js and Kendo UI has been putting the icing on cake of Javascript for this entire time.

So what's the issue?

The issue is not really on the Javascript side; but with HTML. It is entirely apparent that nothing is going to replace HTML when it comes to marking-up web pages. But it lacks the dynamic behavior and the trouble we have to go through to make it such is big, sometimes..

If you have used Javascript/jQuery with HTML, you may have observed a common pattern which we should adhere to make our web apps (Whenever I use the term web apps, be kind enough to think about websites too) dynamic. Lets take a simple example,

In the below example, when a user types his username in a text input, a suggested email address is displayed underneath. I am using an approach using jQuery and another approach using AngularJS. (I apologize because the code is not in proper standard or using best practices. I only need to explain the difference.)

jQuery Approach

html

<div ng-app>
    <input type="text" ng-model="yourName" placeholder="Enter a name here">
    <div>Suggested email : {{yourName + "@gmail.com" }}</div>
</div>

jQuery

$("input#username").keyup(function () {
    $("#suggested_email").html("Suggested email : " + $(this).val() + "@gmail.com");
});

result

AngularJS Approach 

html

<div ng-app>
    <input type="text" ng-model="yourName" placeholder="Enter a name here">
    <div>Suggested email : {{yourName + "@gmail.com" }}</div>
</div>

javascript

Yeah you don't need any Javascript/jQuery for a simple task like this when using AngularJS. The developer should be more focused on the application logic and the best practices. He should not bother implementing and testing trivial DOM manipulation logic.


result

The result is same as above.

So, what is the secret behind this? 

Like I mentioned earlier, AngularJS makes HTML more dynamic. One core feature of AngularJS is the way it handles two-way data binding. In the above example, if we take a look at the jQuery approach, we can see that every time a change occurs in the text input we should explicitly write code to catch this event and manually manipulate the relevant DOM elements accordingly. Just think that if the change of that text input should change 10-15 DOM elements, we have to write more and more code to manipulate all those elements.

Here we were concerned about how to communicate from the html view to the application model. But what if we need to change a DOM element whenever our underlying model changes? Lets say that the suggested email text is displayed according to the result of an AJAX request. So, we have to write another code segment to handle this functionality.

This really messes up our application logic, makes it non-reusable and extensible, and most importantly testing becomes a nightmare.  The two-way data binding mechanism really makes the developer's life easy by allowing the communication between View and Model two way.

2-Way Data Binding of AngularJS

This design not only makes the code well-organized, but also it makes our application much more testable. Because of the fact that DOM manipulation is clearly separated from our business logic, we can plug it out and test separately. Nowadays, applications built upon almost all the programming languages gives a great support for testability. So why don't we make our Javascript based web apps easily testable too?

Conclusion

Here we have discussed about AngularJS by means of its capability to make HTML more dynamic and we had a look at one of its beautiful concepts called 2-way data binding (We'll talk about this in depth in the future). According to what we discussed, I think it is clear that AngularJS is worth giving a try. Not just because, it makes our lives easier, but also it makes sense to build a reusable, extensible and testable application too. In the upcoming articles, we will take the features of AngularJS one by one and discuss about the usefulness of them.

Popular Posts