AngularJS - Introduction to Directives

AngularJS is a Javascript MVC framework from the fine folks over at Google. The focus of Angular is building complex HTML based client applications. Its design philosophy is data first, where your data will be updating the DOM. Contrast this to a framework like JQuery where the DOM will update your data.

AngularJS Logo

This is the ninth in a series of posts on AngularJS where we are using Chemistry data from the periodic table to help us understand the framework. The others posts are

  1. AngularJS - Introduction
  2. AngularJS - Introducing AngularJS Controllers
  3. AngularJS - Introducing NG-Repeat
  4. AngularJS - More with NG-Repeat
  5. AngularJS - Image Binding
  6. AngularJS - Introducing Templates
  7. AngularJS - Introducing Routing
  8. AngularJS - Introduction to Services
  9. AngularJS - Introduction to Directives
  10. AngularJS - Further with Directives
  11. AngularJS - Best Practices
  12. AngularJS - CSS Animations

Note: AngularJS does not allow for more than one ng-app directive. When I have multiple angular posts on the home page of my blog, only one application will work. I need to refactor the entire site to account for this. All of that to say this, you are best clicking on a single article so you can see the pages in action.

On the AngularJS homepage you see the claim that Angular is "HTML Enhanced for Web Apps!" What does this actually mean though? For me, it means directives, which allow us to extend HTML and create new HTML markup. Let us pretend and call it our own HTML 6.

AngularJS HomePage

HTML, when initially created, was a subset of SGML, which is popular in the print industry. HTML, especially in the early days, kept a lot of the print mentality. However, during the period when Web 2.0 applications emerged, it became obvious that HTML was moving way beyond it's print background and becoming an application platform itself.

Given it's print background and laborious standards process that defines the HTML specification, HTML is relatively fixed platform. Your chances of getting a new tag into the standard for use in your application in the next ten years are pretty close to zero. However, Google starts earning the superheroic name it gave AngularJS by solving this problem with directives. To my knowledge, they are the only JavaScript framework that enables you to do this so far.

While we have seen directives previously, they have been attributes that allow us to markup existing HTML elements with bits and pieces of Angular functionality. Examples of this have been ng-show or ng-repeat. For this post, we are going to make a custom directive, which will allow us to create our own HTML tag.

The value of creating our own tags, is that it allows us to start thinking of our HTML markup as a domain specific language. Throughout this series, we have been using Chemistry data to explore Angular. In keeping with that theme, we are going to create a simple custom directive to display chemical data in its own "HTML" tag called periodicchartelement. Cool things are starting to happen here people!

So how do we do this? First, similar to controllers, directives are defined on the module for our application via the module.directive API. Within here, we can setup a small template. Our JavaScript would look something like

.directive('periodicchartelement', function() {
    return {
      template: 'Name: {{element.name}} Atomic Weight: {{element.weight}}'
    };
  });

Of course, we can break the template into its own file by using templateUrl as we have discussed earlier, which is the preferred approach.

We next define what part of our HTML our directive will be expanding. We do this by using restrict to indicate the DOM element we are creating from the following options

  • 'A' - The attribute of a DOM element. for example <div periodicchartelement="element">
  • 'C' - class name
  • 'E' - A new element name, for example <periodicchartelement></periodicchartelement>

There is also the ability creative directives tied to HTML comments with restrict:M. The restrict keyword can also be combined together to in a manner such as ACM, indicating the directive applies to attributes, classes, and comments.

.directive('periodicchartelement', function() {
    return {
    restrict: 'E',
      template: 'Name: {{element.name}} Atomic Weight: {{element.weight}}'
    };
  });

Our directive function now creates a new HTML element, periodicchartelement.

For the example we are creating, we have changed a few things in comparison to previous posts. Based on our introduction of services last time, we have wrapped our periodic data in a service with a function called getElements(). Second, we have expanded the properties of our JSON object used in the application to include fields about the periodicity of chemical elements. Here is an example

  {"atomicNumber": 1,
  "name": "Hydrogen",
  "atomicWeight": 1.00794,
  "phase": "Gas",
  "ionization": 13.5984,
  "melting": -259.14,
  "boiling": -252.87,
  "electronegativity": 2.2,
  "type": 'Non Metal',
  "group": 1,
  "group2": 'IA',
  "period": 1,
  "elecconfig": '1s1',
  "symbol": 'H'},

With that in place, let's create a directive that will allow us to display an element from the periodic table.

The first step is to create the directive function, we will create a new file, chemistryDirective.js and then hang the directive off of our module and call it `periodicchartelement

chemistryApp.directive('periodicchartelement', function (chemistryService) {;
    return {
        restrict: 'E',
        templateUrl: '/2014/06/angularJS-intro-to-directives/template/periodic-template.html',
        scope:{
            element:'=',
            cssType:'=csstypeclass'
        }

    }

});

You will notice several things. First, we are using the restrict keyword to explicitly identify this as a HTML element by using the value of E. Second, we are loading a HTML template for display. Last, we are passing in two items, the element from our JSON object and a cssType, which is a function from our scope to display our CSS class.

Our HTML markup is pretty basic. Notice though where we are setting a CSS class using ng-class and binding to the value of cssType.

<div class='periodicCell' ng-class="cssType">
    <span style="text-align: left"><small>{{element.atomicNumber}}</small></span>&nbsp;&nbsp; <span style="text-align: right"><small>{{element.atomicWeight}}</small></span><br />
    <span style="font-size:24px;"><strong>{{element.symbol}}</strong></span><br />
    <span><small>{{element.name }}</small></span>
</div>

Our CSS type is then a function that determines the CSS class based on the type of element and is defined in our service, chemistryService.js

var getCssClassElement = function ( elementType) {
        var cssClass = '';
        elementType = elementType.toLowerCase();
        cssClass = elementType;
        switch (elementType) {
            case 'metalloids':
                cssClass = 'metalloids';
                break;
            case 'alkali metal':
                cssClass = 'alkaliMetal';
                break;
            case 'non metal':
                cssClass = 'nonMetal';
                break;
            case 'noble gas':
                cssClass = 'nobleGas';
                break;
            case 'halogen':
                cssClass = 'halogen';
                break;
            case 'alkaline earth':
                cssClass = 'alkalineEarth';
                break;
            case 'poor metal':
                cssClass = 'poorMetal';
                break;
            case 'rare earth metal':
                cssClass = 'lathanoids';
                break;
            case 'transition metal':
                cssClass = 'actinoids';
                break;
            case 'alkaline earth metal':
                cssClass = 'poorMetal';
                break;
        }
        return cssClass;
    };


 
Metalloids
Alkali Metal
Non Metal
Noble Gas
Halogen
Alkaline Earth
Poor Metal
Rare Earth Metal
Transition Metal
Alkaline Earth Metal

We have now created a new HTML element! It displays the atomic number, the atomic weight, atomic symbol and the name from our JSON object of periodic data. Based on the element type, we then color code the element appropriately.

This is the most basic of introductions of creating directives with AngularJS, next time, we will dig in deeper!

You can either visit http://angularperiodic.azurewebsites.net/ to see the code in action and as always find the code out on GitHub.


 

AngularJS - Introduction to Services

AngularJS is a Javascript MVC framework from the fine folks over at Google. The focus of Angular is building complex HTML based client applications. Its design philosophy is data first, where your data will be updating the DOM. Contrast this to a framework like JQuery where the DOM will update your data.

AngularJS Logo

This is the eight in a series of posts on AngularJS where we are using Chemistry data from the periodic table to help us understand the framework. The others posts are

  1. AngularJS - Introduction
  2. AngularJS - Introducing AngularJS Controllers
  3. AngularJS - Introducing NG-Repeat
  4. AngularJS - More with NG-Repeat
  5. AngularJS - Image Binding
  6. AngularJS - Introducing Templates
  7. AngularJS - Introducing Routing
  8. AngularJS - Introduction to Services
  9. AngularJS - Introduction to Directives
  10. AngularJS - Further with Directives
  11. AngularJS - Best Practices
  12. AngularJS - CSS Animations

Note: AngularJS does not allow for more than one ng-app directive. When I have multiple angular posts on the home page of my blog, only one application will work. I need to refactor the entire site to account for this. All of that to say this, you are best clicking on a single article so you can see the pages in action.

In AngularJS, when we want to create common code to be shared across our application, we create services. In the Angular world, the controller is the traffic cop, which directs data to your view for binding. Logic for retrieving that data falls to a service.

Services are stateless object that have shared functions that can be used in multiple controllers or views. The functions on services are also available throughout; they can be accessed in directives, controllers, filters, etc.

For an example of a real world service that I have used in the past…. When creating a select list in HTML for an AngularJS application, you usually have an ID associated with a selected element from the list. Often you will display all the properties of the JSON object. In JavaScript, to find this element, you need to loop through all the elements in an array until you get a match on the key. Depending on the size of your application, you end up writing this logic many, many times. To minimize this, I have written a helper application that creates an array that allows for an element to be accessed by a key value, thus reducing the need for repetitive array looping.

Generally, there are two ways to create services within your application. The most common is to use module.service within your application. The second is module.factory. There are a couple of other ways, but we will skip those. AngularJS services are really singleton objects. The object from services are then available across your application via dependency injection, which we will look at soon.

The main difference between the two service creation methods is how they are used. The module.service approach creates an instance of a function. A good use case for this approach is the generic array lookup function mentioned above. The module.factory approach is that the returned value is returned by invoking a function reference. This essentially allows you to treat the service like a class that you can new to make new instances.

The syntax for module.service is

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

//
chemistryApp.service('chemistryService', function(){
    this.elementName= function(element){
        return element.name;
    };
});

//
function ChemCtrl($scope, chemistryService)
{
    ...
    $scope.nameFromService = chemistryService.elementName(elements[0]);
}

The syntax for module.factory is

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

//
chemistryApp.factory('chemistryService', function(){
      return {
            nameFromService: function(element){
               return element.name;
            }
        }
    });

//
function ChemCtrl($scope, chemistryService)
{
    ...
    $scope.nameFromFactory = chemistryService.elementName(elements[0]);
}

Once we create our service, we want to be able to use this within our application. This is done via the magical gremlins that drive the Dependency Injection model in AngularJS. We just pass the service name to our controller when we instantiate it. We showed this above, but just to be sure, by passing chemistryService to the controller, it is service is available within the controller scope

function ChemCtrl($scope, chemistryService)
{
    ...
    $scope.nameFromFactory = chemistryService.elementName(elements[0]);
}

Let's look at a more advanced scenario. I am pretty much stealing this demo from my Skyline Technologies colleague Brian Mahloch. Brian came up with a great demo for demonstrating services using the Periodic Data, which he kindly let me steal.

What we are going to do is determine the type of bonds two elements would make, based on the differences in their electronegativity. We will create a service that does two things, calculate the differences in electronegativty and then based on the difference determine the type of bond.

Our service then looks like

chemistryApp.service('chemistryService', function () {

    this.calculateElectronegativityDifference = function (element1, element2) {

        return Math.abs(element1.electronegativity - element2.electronegativity);

    };

    this.convertElectronegativityDifferenceToName = function (difference) {

        if (difference > 2.0) {
            return 'Ionic Bond';
        } else if (difference >= 0.5 < 1.6) {
            return 'Polar Covalent Bond';
        } else {
            return 'NonPolar Covalent Bond';
        }

    };
});

Our controller creation then gets updated so that we are injecting the service into the parameter list. Next, since $scope is our conduit for the view to talk to the service, we create a function on the controller that will consume the service when we have two elements selected.

chemistryApp.controller('chemServiceCtrl', ['$scope', 'chemistryService',
    function chemServiceCtrl($scope, $log, chemistryService) {

        $scope.elements = periodicData.elements;

        $scope.calculateBondPolarity = function () {

            if ($scope.selectedElement1 && $scope.selectedElement2) {

                $scope.currentBondDifference = chemistryService.calculateElectronegativityDifference($scope.selectedElement1, $scope.selectedElement2);
                $scope.currentBondType = chemistryService.convertElectronegativityDifferenceToName($scope.currentBondDifference);

            }

        };

        /* private methods */

    }]
);

Tying it all together, we have something like this.

Element 1:
Element 2:
You have selected:
{{selectedElement1.name}}
Electronegativity:
{{selectedElement1.electronegativity}}
You have selected:
{{selectedElement2.name}}
Electronegativity:
{{selectedElement2.electronegativity}}
{{selectedElement1.name}} + {{selectedElement2.name}} = {{currentBondType}}

With a electronegativity difference of {{currentBondDifference | number:2}} {{selectedElement1.name}} and {{selectedElement2.name}} would form a {{currentBondType}}

We can now start to see how AngularJS provides a platform for creating web applications. With services, we are able to encapsulate logic and use it within multiple controllers in our application.

You can either visit http://angularperiodic.azurewebsites.net/ to see the code in action and as always find the code out on GitHub.


 

John Ptacek I'm John Ptacek, a software developer for Skyline Technologies. This blog is my contains my content and opinionss, which are not those of my employer.

Currently, I am reading The Dark Forest by Cixin Liu (刘慈欣)

@jptacekGitHubLinkedInStack Overflow