Protractor: The Secret Service


This post is not about how to install and use Protractor. I believe setting up Protractor is a lot easy, so heading over to the documentation is an ideal choice to make. For those who have not heard of Protractor yet, it is nothing but an end-to-end test framework for AngularJS applications. However, it works seamlessly for non-angular applications as well, obviously, with a small tweak which is what this post is all about.

In a nutshell, Protractor runs tests against your application running in a real browser, interacting with it as a user would. That means you have to give a set of instructions for each test you write. But before that, let us create a small demo in jQuery (no AngularJS). Below demo is fairly simple but useful to put my point across about robustness of writing tests in Protractor. In this demo, we are having a dummy Google Sign In button loaded via a fake REST call to Google Developers APIs.

<br>
&lt;!DOCTYPE html&gt;<br>
&lt;html&gt;<br>
&lt;head&gt;<br>
	&lt;title&gt;Protractor: The Secret Service&lt;/title&gt;<br>
	&lt;script src="http://code.jquery.com/jquery-1.11.3.min.js"&gt;&lt;/script&gt;<br>
	&lt;script&gt;<br>
	$(window).load(function() {<br>
		// Simulating a REST call with Google APIs<br>
		window.setTimeout(function() {<br>
			$('#signin-placeholder').replaceWith(<br>
				'&lt;button id="signin"&gt;Google Sign In&lt;/button&gt;'<br>
			);</p>
<p>			$('#signin').click(function() {<br>
				$(this).text('Sign Out');<br>
			});<br>
		}, 2000);<br>
	});<br>
	&lt;/script&gt;<br>
&lt;/head&gt;<br>
&lt;body&gt;<br>
	&lt;div id="signin-placeholder"&gt;&lt;/div&gt;<br>
&lt;/body&gt;<br>
&lt;/html&gt;<br>

Sleep

Now that we understood how our application works, it’s time to write E2E tests for it. Following test block might look sound and easy to grasp to you, but there are couple of hidden tricks have been used in order to pass it.

<br>
describe('Non EC Tests', function() {<br>
	beforeEach(function() {<br>
		browser.ignoreSynchronization = true;<br>
		browser.get('demo.html');<br>
	});</p>
<p>	it('Should', function() {<br>
		browser.sleep(2 * 1000);<br>
		element(by.id('signin')).click();<br>
		expect(element(by.id('signin')).getText()).toBe('Sign Out');<br>
	});<br>
});<br>

First line in the beforeEach block i.e. browser.ignoreSynchronization is to tell Protractor that, Hey, this is not an Angular application, so do not wait for Angular to load. Start running tests as soon as the page loads in a browser. This is the one and only trick to consider while using Protractor for non-angular applications which means if in case a webdriver control flow hangs while running tests, that’s the thing to look for in the Spec. Moving on to the it block which is our actual test. In the beginning, we make sure to wait for 2 seconds as per our business logic before we click the login button. Later we confirm the button text is changed to Sign Out.

The important thing to note here is that the browser.sleep method is a last resort most people use to make tests pass to prevent random failures, however, in our case, it is tightly coupled with the business logic. If we increase/decrease the timeout in the fake REST call, we’ll have to adjust the same to fix the broken test. In addition to it, the real REST call may or may not complete within the expected timeout making your test unstable. Let’s fix that…

Wait

Selenium webdriver exports a method called browser.driver.wait that schedules a command to wait for a condition to hold true. This means wait will repeatedly evaluate the condition until it returns a truthy value. You can pass a condition or a custom function that returns a promise to it. So we will simply replace the browser.sleep statement with,

<br>
browser.wait(element(by.id('signin')).isDisplayed, 2 * 1000);<br>

In here browser will wait until isDisplayed method returns a truthy value. If it did not, it will time out after 2 seconds. Note in protractor version 2.0.0, due to changes in WebDriverJS, wait without a timeout will now default to waiting for 0 ms – which off-course forbids our business logic. So we chose to provide ideal explicit timeout. To your surprise the above statement will throw, Failed: No element found using locator: By.id("signin") error because it’s not available in the DOM yet. We could have used browser.waitForAngular method for Angular to render the button if we were using it in the demo. Protractor deals with such scenarios nicely, especially in Angular applications wherein it forces the webdriver to wait until Angular has finished rendering and has no outstanding $http or $timeout calls before continuing.

ExpectedConditions

No worries..! Selenium offers a different mechanism to handle the same for Protractor called as ExpectedConditions a.k.a. EC. It’s similar to protractor isDisplayed API that we saw earlier but especially for non-angular applications. Again the webdriver waits until the expected condition returns a truthy value. Moreover, you can mix it with multiple and, or, and not operators. So to fix the error, we’ll use,

<br>
browser.wait(protractor.ExpectedConditions.visibilityOf(<br>
  element(by.id('signin'))<br>
));<br>

This will schedule a command for the webdriver to wait and repeatedly evaluate the condition to check if the element is available/visible in the DOM. There are various functions EC supports that you should take a look at http://www.protractortest.org/#/api?view=ExpectedConditions.

Wrap up

So that’s it. In a real-world project, I happened to encountered similar issues on staging environment where pages load slighly slow than local webserver and some of my tests started failing randomly because browser.sleep hack for HTTP or AJAX calls did not pan out :-). EC statements have helped me a lot while writing robust Protractor tests for non-angular apps and made tests more stable too, mostly, on Firefox and Safari.

Thanks for reading and keep Rocking \m/.

If you found this article useful in anyway, feel free to donate me and receive my dilettante painting as a token of appreciation for your donation.

Building Pluggable Components in AngularJS


Although ng-boilerplate or angular-seed talks about the best-practice directory structure to ensure code re-usability and maximum scalability but sometimes having a separate directory per feature seems an ideal choice because it allows us to integrate loosely coupled components together and makes it easy to unplug it anytime without much hassle. The approach I’m proposing here may not be the best practice but it solves the real problem for us.

In our application, we’ve couple of icons (tools) grouped by a controller and whenever we wish to move any icon/tool from one controller to another we’d to migrate the respective code base also which was quite time consuming and risky (sometimes it breaks the feature during migration).

What we wanted to create was a pluggable component. By just including the JS file of the component (similar to Polymer Import) and placing an element anywhere should make the feature available.

So, I came up with the following directory structure:
app/scripts
└── features
│   ├── home
│   │   ├── home.js
│   │   └── home.tpl.html
│   └── profile
│   │   ├── profile.js
│   │   └── profile.tpl.html

Creating a Component

In angular, writing a custom directive is the best option to create a component. So lets begin with how we can use a particular component once it is ready.

<br>
&lt;!-- index.html --&gt;<br>
&lt;div ng-controller="OneCtrl"&gt;<br>
    &lt;div pc-home&gt;&lt;/div&gt;<br>
&lt;/div&gt;<br>

As I said earlier, in order to make it as pluggable as possible we’ll create a separate module for it so whenever required the module will be injected as a dependency. So below is the blueprint of the component:

<br>
// home.js<br>
angular.module('tools.home', [])<br>
  .directive('pcHome', function() {<br>
    return {<br>
      restrict: 'EA',<br>
      replace: true,<br>
      scope: {},<br>
      controller: function($scope, $element, $attrs) {</p>
<p>      },<br>
      template: '',<br>
      link: function(scope, element, attrs) {</p>
<p>      }<br>
    }<br>
  });<br>

We restricted the component to be used as an Element or Attribute, set an isolate scope for it so that it does not interfere with its parent scope (OneCtrl in this case – Prototypal Inheritance will not work either). The controller option will allow us to write all the logic related to the component and finally a template/templateUrl will replace the existing element on which the directive was placed.

We also want to activate a single tool at a time so in order to maintain the state of the tool, we’ll create a service just to share data between multiple components. openPanel property handles the state of the tool that is currently in use or selected.

<br>
App.factory('StateService', function() {<br>
  return {<br>
    openPanel: ''<br>
  };<br>
});<br>

We’re going to use a button as a tool handler replacing the div[pc-home]. You can also use templateUrl in case your template is fat. As you can notice, we’ve injected StateService as a dependency for the component which then bound to $scope inside controller and later used within the template to toggle a CSS class.

<br>
angular.module('tools.home', [])<br>
  .directive('pcHome', function(StateService) {<br>
    return {<br>
      restrict: 'EA',<br>
      replace: true,<br>
      scope: {},<br>
      controller: function($scope, $element, $attrs) {<br>
        $scope.stateService = StateService;<br>
      },<br>
      template: '&lt;button class="btn home" ng-click="toggle()" ng-class="{true: \'btn-primary\', false: \'btn-success\'}[stateService.openPanel == \'home\']"&gt;Home&lt;/button&gt;',<br>
      link: function(scope, element, attrs) {</p>
<p>      }<br>
    }<br>
  });<br>

Lets define toggle() method to perform some kind of action once the button is clicked. We’re just updating the property value of the StateService here. You can go and check the demo – click Home button and see it toggles its state when clicked. But as of now, the button does not do anything except toggling the background color.

<br>
angular.module('tools.home', [])<br>
  .directive('pcHome', function(StateService) {<br>
    return {<br>
      restrict: 'EA',<br>
      replace: true,<br>
      scope: {},<br>
      controller: function($scope, $element, $attrs) {<br>
        $scope.stateService = StateService;</p>
<p>        $scope.toggle = function() {<br>
          if (StateService.openPanel === 'home') {<br>
            StateService.openPanel = '';<br>
          } else {<br>
            StateService.openPanel = 'home';<br>
          }<br>
        };<br>
      },<br>
      template: '&lt;button class="btn home" ng-click="toggle()" ng-class="{true: \'btn-primary\', false: \'btn-success\'}[stateService.openPanel == \'home\']"&gt;Home&lt;/button&gt;',<br>
      link: function(scope, element, attrs) {</p>
<p>      }<br>
    }<br>
  });<br>

Final Trick

It is sometimes important to position an element relative to <body> not its parent. And thats why we can not merge the panel that we want to open ( when the button turns blue) inside the template. For that, we’ll have to add the panel markup dynamically inside index.html so that its available only if the component is in use or plugged. Lets put the panel markup in separate file named home.tpl.html.

<br>
&lt;!-- home.tpl.html --&gt;<br>
&lt;div class='home-template' ng-show="stateService.openPanel == 'home'"&gt;<br>
  &lt;ul&gt;<br>
    &lt;li ng-repeat="choice in choices" ng-bind-template="{{$index + 1}}. {{choice}}"&gt;&lt;/li&gt;<br>
  &lt;/ul&gt;<br>
&lt;/div&gt;<br>

And finally we’ll change the link function where we’re simply injecting a

tag with ng-include attribute on it which allows us to include the template in after getting compiled by $compile service with the same scope. The shared scope lets us use any method/property defined in our controller within the template as well which obviates the need for a separate controller specifically for home.tpl.html.
<br>
angular.module('tools.home', [])<br>
  .directive('pcHome', function(StateService, $compile) {<br>
    return {<br>
      restrict: 'EA',<br>
      replace: true,<br>
      scope: {},<br>
      controller: function($scope, $element, $attrs) {<br>
        $scope.stateService = StateService;</p>
<p>        $scope.toggle = function() {<br>
          if (StateService.openPanel === 'home') {<br>
            StateService.openPanel = '';<br>
          } else {<br>
            StateService.openPanel = 'home';<br>
          }<br>
        };</p>
<p>        $scope.choices = [<br>
         'Share whats new...',<br>
         'You may know',<br>
         'Updates from followers',<br>
         'Few posts from communities',<br>
         'Scrolling now for more updates',<br>
         'Loading...'<br>
        ];<br>
      },<br>
      template: '&lt;button class="btn home" ng-click="toggle()" ng-class="{true: \'btn-primary\', false: \'btn-success\'}[stateService.openPanel == \'home\']"&gt;Home&lt;/button&gt;',<br>
      link: function(scope, element, attrs) {<br>
        angular.element(document.getElementsByTagName('body')).append(<br>
          $compile('&lt;div ng-include="\'home.tpl.html\'"&gt;&lt;/div&gt;')(scope)<br>
        );<br>
      }<br>
    }<br>
  });<br>

Word of Wisdom

By design the ng-include directive creates a new scope and in our case it will become a child scope of the original scope we compiled the template with. So beware of initializing a model inside the template itself using ng-model or ng-init. Is there a fix for this?

Yes! suppose you want to use ng-model="search" within the template then use $parent with it. i.e. ng-model="$parent.search". This will automatically bind the search model on the controller’s scope not the one created by ng-include.

The End

Last but not the least, whenever we need this component in our application we just have to resolve the dependency by introducing App.tools in main application module and you are free to use the component anywhere within the application without breaking a thing. Try moving components around.

<br>
var App = angular.module('App', ['App.tools']);<br>
angular.module('App.tools', ['tools.home', 'tools.profile']);<br>

In case you want to unplug the component then just delete the features/home directory, unreference the home.js and

from index.html, remove the module tools.home, and you are DONE.

If you found this article useful in anyway, feel free to donate me and receive my dilettante painting as a token of appreciation for your donation.

How I inspire my colleagues to learn AngularJS


Since months I was trying to convince my colleagues to learn AngularJS but they were putting it off by saying:

  • Its hard to learn
  • It can not be as cool as jQuery
  • We are happy with Backbone.js

But I knew that one day I’ll prove my point because an action is worth a thousand words. And I was waiting for that moment until…

The day arrived

We’d released the product we’re developing since months in Backbone.js and everything was well and good. The application was feature rich and huge in terms of code base – including lots of popups, forms, grids wrapped in Underscore.js templates. That day my boss came to me and started talking about a new feature the client has asked to implement urgently. They wanted to add more themes to our application and the worst part was that a theme color supposed to be come from an XML file so it could be any random color. This means we’d to rely on Javascript instead of CSS to apply the theme color across the application such as popups headers or buttons etc.

Everybody was worried because there was no way we could deliver it as quickly as possible and even we do it, we had to write lots of code and in lot many places.

I thought over for some time and recalled that Angular Directive could be a good fit for this kind of problem so I quickly jotted down a very simple piece of code:

  App.run(function($rootScope) {
    // Stored a theme color after reading it from an XML
    $rootScope.themeColor = '#BADA55';
  });

  App.directive('themify', function($rootScope) {
    return {
      restrict: 'A',
      link: function(scope, element, attrs) {
        var arrProp = attrs.themify.split(',');

        arrProp.forEach(function(prop) {
          element.css(prop, $rootScope.themeColor);
        });
      }
    }
  });

Then tried to test it by applying the directive on a header of one of the popups:

    <div themify="border-color,color">Add Bookmark</div>

It worked!!!. Finally we used the same directive all over the application. There was one little problem though wherein angular was ignoring all the directives placed inside Underscore templates. Later I figured I had to compile the templates using $compile. A bit hackish approach I used in order to access the $rootScope and $compile method inside Backbone.View is to make them global – $grootScope and $gcompile respectively.

  App.run(function($rootScope, $compile) {
    App.$gcompile = $compile;
    App.$grootScope = $rootScope;
  });
  AB.listView = Backbone.View.extend({
    el: 'div.list',

    template: _.template($('#listTemplate').html()),

    render: function () {
      this.$el.html(this.template({}));

      // Port it to Angular world
      App.$gcompile(this.$el)(App.$grootScope);
    }
  });

Lesson learned

Do not force people, inspire them.

Although, it just took an hour to bake this new feature but my colleagues were impressed and inspired to give AngularJS a try. And I was quite happy to help them get going with Angular. Love yo’all!

If you found this article useful in anyway, feel free to donate me and receive my dilettante painting as a token of appreciation for your donation.

Migration guide for jQuery Developers


There are couple of links explaining AngularJS to jQuery developers and my effort also goes to do the same. This post is dedicated to all the jQuery developers who wish to know AngularJS before they dive in. I myself worked on jQuery since a very long time and literally its hard to switch to something which is radically different and way cooler than jQuery (sorry, darling!).

Document is Ready?

We all know about $(document).ready() or $(window).load() methods to setup a safe wrapper around the code to make sure that everything runs after the DOM is loaded.
Also, you might have used literal styled javascript with jQuery:

var App = {
  run: function() {
    var bootstrapper = 'You have successfully bootstrapped jQuery';
    $('div.alert').html(bootstrapper);
  }
};

// Finally I'll call the main method
$(document).ready(function() {
  App.run();
});

Do not panick! Angular itself provides the manual bootstrapping the same way. Angular advocates modularity so that every piece of an application should have its own module and there is a main module that holds rest of the modules together. The subsequent code defines the main module (similar to jQuery example above) and run() block is the built-in (sort of) main method in Angular which runs once the application is bootstrapped.

  // Define a module, App
  var App = angular.module('App', []);

  // Built-in run method
  App.run(function($rootScope) {
    $rootScope.bootstrapper = 'You have successfully bootstrapped AngularJS';        
  });

  // Finally bootstrap angular manually
  angular.element(document).ready(function() {
    angular.bootstrap(document, ['App']);

    // Target an element instead
    // angular.bootstrap(jQuery('body'), ['App']);
  });

The one thing to notice here is $rootScope – which is a global variable you can bind properties to that can be injected in other modules for further use.

Finally we’ll use angular’s templating syntax {{ }} to declaratively place data into the markup instead of grabbing the element explicitly like jQuery. Can you see?

<div class="alert alert-success">{{bootstrapper}}</div>

In case you are not a fan (like me) of manual bootstrapping, you can make it automatic as well by setting ng-app directive on any element such as html, body etc. Angular looks for an element having ng-app on it and uses it as a target during bootstrapping. Now you do not need angular.element(document).ready() block as defined above.

<html ng-app="App">

XHR

AJAX is the second most important thing people use jQuery for. jQuery provides different methods to fire up an XHR call.

In angular, there is $http service which is a wrapper around the browser’s XMLHttpRequest object that returns promises .then(), .success(), and .error() based on the request was successful or failed.

The only difference is that you have to manually inject it into the module definition. Suppose I want it inside the run() block we’d seen before:

  App.run(function($rootScope, $http) {
    $http.get('foobar.php').success(function(data, status) {
      // I succedded
    }).error(function(data, status) {
      // I failed
    });
  });

Do not touch the DOM

jQuery was made to do so, Angular is not!

In AngularJS, try to avoid touching DOM implicitly as much as possible so that you can fully leverage the 2-way data binding between your data and the DOM. We are going on journey to build a process order page using following directives.

ng-model

In jQuery, .val() is a setter/getter method to interact with form controls which was quite amazing but it gets worst when you want to show the value elsewhere instantly, means we had to bind keypress events on the input control that will update the DOM as expected. But its too much effort as well as code for such a small task.

Angular provides a neat directive to rule them all. Always use ng-model in order to read values out of form controls. You do not have to manually select an element to set/get its value – Just play with plain old javascript object/array.

<input type="text" ng-model="name">
App.run(function($rootScope) {
  // setting the default value for the input
  $rootScope.name = 'AngularJS';

  // this will return the existing value of the input
  // console.log($rootScope.name);
});

ng-options

Use to lay out options based on model for select element. Know more about it.

App.run(function($rootScope) {
  $rootScope.cities = [
    {id: 'NM', name: 'Navi Mumbai'},
    {id: 'PN', name: 'Pune'}
  ];
});
<select ng-model="city" ng-options="c.name for c in cities"></select>

ng-class

Toggle CSS classes based on expression. This is a very common thing in jQuery:

if (same) {
  $('small').removeClass('strike');  
} else {
  $('small').addClass('strike');
}

In angular, you can use ng-class directive to apply CSS classes conditionally. If same is boolean true then apply strike class. Otherwise remove it.

<small ng-class="{'strike': !same}">same as billing</small>

ng-disabled

Similar to ng-class, you can disable/enable form controls using ng-disabled directive.
In jQuery,

$('[ng-model="shipping_name"]').prop('disabled', same);

In angular,

<input ng-disabled="same" 
       type="text" 
       ng-model="shipping_name" 
       placeholder="Full Name">

ng-change

You may want to reflect your billing address as shipping address instantly.
In jQuery,

<input onkeypress="$('[ng-model="shipping_name"]').val($('[ng-model="billing_name"]').val());" 
       type="text" 
       ng-model="billing_name"> 

In Angular,

<input ng-change="reflect()" type="text" ng-model="billing_name">
$rootScope.reflect = function() {
  $rootScope.shipping_name = $rootScope.billing_name;
  $rootScope.shipping_address = $rootScope.billing_address;
  $rootScope.shipping_city = $rootScope.billing_city;
};

ng-click

In jQuery,

$('div.btn-primary').click(function() {
  processed = true;
});

In Angular,

<button class='btn btn-primary' ng-click="processed = true">Process Order</button>

ng-show/ng-hide

This is again very common thing in jQuery to show/hide elements based on conditions.
In jQuery,

if (processed) {
  $('div.alert').show();
} else {
  $('div.alert').hide();
}

In Angular,

<div class="alert alert-success" ng-hide='!processed'>
  <b>Well done!</b> We've successfully processed the order.
</div>

Here is the Final Demo of our Process Order form using all above directives.

Loading partials

Many of us often create a separate header and footer partials to be injected into many pages instead of repeating all over again.
In jQuery,

<body>
    <div id='header'></div>
    <script type="text/javascript">
      $('#header').load('header.html');
    </script>

    <!-- Body -->

    <div id='footer'></div>
    <script type="text/javascript">
      $('#footer').load('footer.html');
    </script>    
</body>

In Angular, ng-include works similar to jquery.load but also allows to compile and include an external HTML fragment. Do not forget to wrap your html fragment in single quotes as I’ve already wasted an hour to figure that out 😦

<body>
    <div ng-include="'header.html'"></div>

    <!-- Body -->

    <div ng-include="'footer.html'"></div>
</body>

History – page navigation

Take an example of a simple addressbook application that contains 3 links shown below and div.abPanel is the place where you would wish to load the appropriate template.

<div class='cell abLinks'>
    <a href='javascript:void(0);' id='addNewContact'>Add New Contact</a><br />
    <a href='javascript:void(0);' id='listContacts'>List all Contact</a><br />
    <a href='javascript:void(0);' id='searchContacts'>Search any Contact</a><br />
</div>
<div class='cell abPanel'>Loading... Please Wait.</div>

In jQuery, you would probably do this.

$('#addNewContact').click(function () {
  $('div.abPanel').load('add_new_contact.html', function () {
    // do all DOM manipulations or event bindings here
  });
});

In Angular, we’ll use awesome $routeProvider service without writing much boilerplate code. This simply loads a template based on hash.

App.config(function ($routeProvider) {
  $routeProvider
    .when('/add', { templateUrl: 'partials/add.html' })
    .when('/list', { templateUrl: 'partials/list.html' })
    .when('/search', { templateUrl: 'partials/search.html' })
    .otherwise({
      redirectTo: '/add'
    });
});

And finally change above links a little bit to:

<div class='cell abLinks'>
    <a href='#/add'>Add New Contact</a><br />
    <a href='#/list'>List all Contact</a><br />
    <a href='#/search'>Search any Contact</a><br />
</div>
<div ng-view>Loading... Please Wait.</div>

The one thing to notice here is ng-view directive that lets your render the template of the current route automagically.

Animation

The .animate() method allows us to create animation effects in jQuery. In order to fade out the process order page while hiding, we can write something like this:

$('div.row').animate({opacity: 0}, 1000);

In angular(version 1.1.4+), ng-animate allows us to control the animation either using CSS or JS. This directive can gel up with other directives like ng-show/ng-hide, ng-view, ng-include, ng-switch, etc.
We’ll simply add it to our div.alert which takes processed CSS class as an option.

<div class="alert alert-success" ng-show='processed' ng-animate="'processed'">
  <b>Well done!</b> We've successfully processed the order.
</div>

Then we’ll define necessary CSS classes. What happens here is when div.alert is about to be shown (means processed becomes boolean true), both processed-show and processed-show-active classes will be applied which will help fade in the element for 0.5 seconds and later be removed. And display property sets to block.

.processed-show {
  -webkit-transition:all linear 0.5s;
  -moz-transition:all linear 0.5s;
  -ms-transition:all linear 0.5s;
  -o-transition:all linear 0.5s;
  transition:all linear 0.5s;
  opacity:0;
}
.processed-show-active {
  opacity:1;
}

We can use similar css while hiding the element but opacity will change from 100% to 0%.

.processed-hide {
  -webkit-transition:all linear 0.5s;
  -moz-transition:all linear 0.5s;
  -ms-transition:all linear 0.5s;
  -o-transition:all linear 0.5s;
  transition:all linear 0.5s;
  opacity:1;
}
.processed-hide-active {
  opacity:0;
}

Check out the demo. There are more in-depth articles on yearofmoo.com and nganimate.org.

jQuery plugins

We can not live without it. A few weeks ago, I’d written an article on how to use jQuery plugin the angular way.

Custom Directives

AngularJS really shines where it lets you write custom directives also. There are hell lot of articles to get you started.

Wrap ups

I’d seen many of my colleagues struggled to get going with AngularJS and hence I’d decided to write this post. Time has come to expand the horizon. All the best.

If you found this article useful in anyway, feel free to donate me and receive my dilettante painting as a token of appreciation for your donation.

The Hitchhiker’s Guide to the Directive


AngularJS Directive is what we have close to Web Components at this time and is the most crucial and difficult piece in AngularJS. I’ve not found any suitable article on the web to fully understand directives behavior based on options it takes so I’m writing one. Fingers crossed!

As you may know that directives can be used as an element, an atttribute, a class name, and a comment. Here are the ways we can use our directive angular in HTML:

<div angular></div>
<div class="angular"></div>
<angular></angular>
<!-- directive: angular -->

For a huge name such as this, you can split the words using hyphens while declaring and camel cased it while defining a directive.

<div the-quick-brown-fox-jumps-over-the-lazy-dog></div>
App.directive('theQuickBrownFoxJumpsOverTheLazyDog', function() {... });

A Quick Demo to load AngularJS logo using the above directive. For your reference, I’m simply injecting an IMG tag here.

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

    App.directive('angular', function() {
      return {
        restrict: 'ECMA',
        link: function(scope, element, attrs) {
          var img = document.createElement('img');
          img.src = 'http://goo.gl/ceZGf';


          // directives as comment
          if (element[0].nodeType === 8) {
            element.replaceWith(img);
          } else {
            element[0].appendChild(img);            
          }
        }
      };
    });

How Directive works?

In short, “AngularJS when bootstrapped, looks for all the directives built-in as well as custom ones, compiles them using $compile() method (which keeps track of all the directives associated with an element, sorts them by priority and produces their link function), and links with scope (created by ng-app, ng-controller, ng-include, etc) by registering listeners or setting up $watches resulted in 2 way data bindings between the scope and the element.”

Blueprint?

We’ll take a look at it briefly soon but meanwhile, here is the Directive definition:

var myModule = angular.module(...); 
myModule.directive('directiveName', function (injectables) {
  return {
    restrict: 'A',
    template: '<div></div>',
    templateUrl: 'directive.html',
    replace: false,
    priority: 0,
    transclude: false,
    scope: false,
    terminal: false,
    require: false,
    controller: function($scope, $element, $attrs, $transclude, otherInjectables) { ... },
    compile: function compile(tElement, tAttrs, transclude) {
      return {
        pre: function preLink(scope, iElement, iAttrs, controller) { ... },
        post: function postLink(scope, iElement, iAttrs, controller) { ... }
      }
    },
    link: function postLink(scope, iElement, iAttrs) { ... }
  };
});

Now we’ll explore each option aforementioned one by one. You will find working demos along the way as well.

Compile function

For an AngularJS newbie (including myself), its quite confusing to understand compile and link functions in the first place. I initially thought that we can use both the functions in a directive simultaneously which is completely wrong. In fact, a compile function produces a link function. This is the place where you can do the DOM manipulation mostly. This takes 2 arguments by default:

  • tElement – A template element the directive has been declared on.
  • tAttrs – A list of attributes associated with the tElement.

In addition, you can inject transcludeFn, directiveController, etc. in it. Here is the code snippet for your reference:

<div compile-check></div>

Notice that, tElement is a jquery object so you do not have to wrap it in $ again.

   App.directive('compileCheck', function() {
      return {
        restrict: 'A',
        compile: function(tElement, tAttrs) {
          tElement.append('Added during compilation phase!');
        }
      };
    });

Link function

Its job is to bind a scope with a DOM resulted in a 2-way data binding. You have access to scope here unlike compile function so that you can create custom listeners using $watch method. As expected it takes 3 arguments:

  • scope – A scope to be used by the directive.
  • element – An element the directive is bound to.
  • attrs – A list of attributes associated with the element.

Similar to compile function, you can inject transcludeFn, directiveController, etc. in it.

Why there is a need for a compile function then?

The only reason for the existence of a compile function is performance. Take an example of a directive which consumes a collection and lays out DOM for each item of the collection (similar to ng-repeat). So, if we somehow chain the DOM for each item with a scope then it’ll slow down the whole process because as soon as the scope is created and bound to the DOM – it started watching for change in data. You can imagine how terribly it’ll degrade the performance. This problem is resolved by having 2 way process, a compile method which runs only once for each item and a link method which runs every time the model changes. So, all items will be compiled first and linked later altogether.

Restrict – to be or not to be?

This simply restricts the way you can define a directive. As we’ve seen before, we can restrict it as:

  • E: elements
  • A: attributes
  • C: class names (CSS)
  • M: comments

There are different ways to make it HTML5 compliant also. Below is the angular’s built-in directive named, ng-bind:

<span ng:bind="name"></span>
<span ng_bind="name"></span>
<span ng-bind="name"></span>
<span data-ng-bind="name"></span>
<span x-ng-bind="name"></span>

Template

This basically replaces the existing contents of an element. Imagine you want to load your github profile data in a nice card like format in many places on your website but do not want to repeat the same markup all over again. Also, your template can use data bound to $scope in a controller or $rootScope unless you have not set scope option (we’ll see it soon) explicitly in the directive definition.

In the below example, I’m simply fetching the angular.js repo information using the github API.

<div ng-controller="DemoCtrl">
  <div whoiam>This text will be overwritten</div>
</div>

In the directive, you can see that we have not created a new scope for the directive so that the data has been shared between the controller and the directive because its sharing the same scope. You can even override the values for header as well as footer within a link function. Go and break things.

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

// Similar to $(document).ready() in jQuery
App.run(function($rootScope) {
  $rootScope.header = 'I am bound to rootScope';
});

App.controller('DemoCtrl', function($scope) {
  $scope.footer = 'I am bound to DemoCtrl';
});

App.directive('whoiam', function($http) {
  return {
    restrict: 'A',
    template: '{{header}}<div class="thumbnail" style="width: 80px;"><div><img ng-src="{{data.owner.avatar_url}}"/></div><div class="caption"><p>{{data.name}}</p></div></div>{{footer}}',
    link: function(scope, element, attrs) {
      $http.get('https://api.github.com/repos/angular/angular.js').success(function(data) {
        scope.data = data;
      });
    }
  };
});

TemplateUrl

If we keep adding more and more details to above card, maintaining the template markups within the directive’s definition will become complicated and unmanageable. So, its better to keep our template in a separate html file and reference its location in templateUrl. As the template loading is asynchronous in this case, so the compilation/linking will be delayed until the template is fully loaded, and will be cached in $templateCache for later use.

You can either maintain a separate file as mentioned before or wrap it in <script> tags just like handlebars does. Here is the syntax:

<script type='text/ng-template' id='whoiam.html'></script>

Make sure you set the proper type otherwise angular will look for the file (specified as id, in this case) on your file system.

In the following example, I’m showing more details on the card. Make a bee line for the demo.

<script type='text/ng-template' id='whoiam.html'>
    <div class="thumbnail" style="width: 260px;">
      <div><img ng-src="{{data.owner.avatar_url}}" style="width:100%;"/></div>
      <div class="caption">
        <p><b>Name: </b>{{data.name}}</p>
        <p><b>Homepage: </b>{{data.homepage}}</p>
        <p><b>Watchers: </b>{{data.watchers}}</p>
        <p><b>Forks: </b>{{data.network_count}}</p>
      </div>
    </div>
 </script>

Replace

In the below screenshot of the example we’d seen before, you can see the elements having angular directives on them, which are sometimes unnecessary after the directives are compiled by AngularJS.

Without replace option
Without replace option

Thats where replace option comes handy – If set to true will replace the element having a directive on it with a template. This is what you see after using replace: true. Check out the demo

With replace option
With replace option

You have to use templateUrl/template along with replace.

Priority

As I said earlier, AngularJS finds all directives associated with an element and processes it. This option tells angular to sort directives by priority so a directive having higher priority will be compiled/linked before others. The reason for having this option is that we can perform conditional check on the output of the previous directive compiled. In the below example, I want to add `btn-primary` class only if a div has `btn` class on it.

<div style='padding:100px;'>
  <div primary btn>The Hitchhiker’s Guide to the Directive Demo</div>
</div>

Please note that the default priority if not set will be zero. In this example, btn directive will be executed before primary. Play with the demo!

App.directive('btn', function() {
  return {
    restrict: 'A',
    priority: 1,
    link: function(scope, element, attrs) {
      element.addClass('btn');
    }
  };
});

App.directive('primary', function() {
  return {
    restrict: 'A',
    priority: 0,
    link: function(scope, element, attrs) {
      if (element.hasClass('btn')) {
        element.addClass('btn-primary');
      }
    }
  };
});

Terminal

As per the official documentation, If set to true then the current priority will be the last set of directives which will execute on an element. It holds true unless you use custom directives in conjunction with built-in directives having priority set on them such as ngRepeat, ngSwitch, etc. Instead all custom directives having a priority greater than or equal the current priority will not be executed in this case.

In the below example, first has a higher priority than second – which has terminal set to true. And if you set the lower priority to first – It will not be executed at all. But in case of no-entry directive, it will not be executed even though it has a higher priority than ng-repeat. Is it a bug? Is it because of transclusion used in ng-repeat? Need to dig in…

<div first second></div>

<ul>
    <li ng-repeat="item in ['one', 'two', 'three']" no-entry>{{item}} </li>
</ul>
    App.directive('first', function() {
      return {
        restrict: 'A',
        priority: 3,
        link: function(scope, element, attrs) {
          element.addClass('btn btn-success').append('First: Executed, ');
        }        
      };
    });

    App.directive('second', function() {
      return {
        restrict: 'A',
        priority: 2,
        terminal: true,
        link: function(scope, element, attrs) {
          element.addClass('btn btn-success').append('Second: Executed ');
        }        
      };
    });

    App.directive('noEntry', function() {
      return {
        restrict: 'A',
        priority: 1001,
        link: function(scope, element, attrs) {
          element.append('No Entry: Executed ');
        }        
      };
    });

Controller

This can be treated as a control room for a directive. You can either bind properties/methods to $scope available or this keyword. The data bound to this will be accessible in other directives by injecting the controller using require option. In below example, I’m toggling the state of a lightbulb so that child directives will know about the current state by calling getState() method – we’ll see it soon.

App.directive('powerSwitch', function() {
      return {
        restrict: 'A',
        controller: function($scope, $element, $attrs) {
          $scope.state = 'on';

          $scope.toggle = function() {
            $scope.$apply(function() {
              $scope.state = ($scope.state === 'on' ? 'off' : 'on');
            });
          };

          this.getState = function() {
            return $scope.state;
          };
        },
     };
});

Require

This lets you pass a controller (as defined above) associated with another directive into a compile/linking function. You have to specify the name of the directive to be required – It should be bound to same element or its parent. The name can be prefixed with:

  • ? – Will not raise any error if a mentioned directive does not exist.
  • ^ – Will look for the directive on parent elements, if not available on the same element.

Use square bracket [‘directive1’, ‘directive2’, ‘directive3’] to require multiple directives

Here is a demo to turn a lightbulb on/off:

  <div class="btn btn-success" power-switch>
    {{'Switched ' + state | uppercase}}

    <div lightbulb class='bulb' ng-class="bulb"></div>
  </div>

The below code is self explanatory. She loves me, She loves me not!!

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

    App.directive('powerSwitch', function() {
      return {
        restrict: 'A',
        controller: function($scope, $element, $attrs) {
          $scope.state = 'on';

          $scope.toggle = function() {
            $scope.$apply(function() {
              $scope.state = ($scope.state === 'on' ? 'off' : 'on');
            });
          };

          this.getState = function() {
            return $scope.state;
          };
        },
        link: function(scope, element, attrs) {
          element.bind('click', function() {
            scope.toggle();
          });

          scope.$watch('state', function(newVal, oldVal) {
            if (newVal === 'off') {
              element.addClass('disabled');
            } else {
              element.removeClass('disabled');
            }
          });
        }  
      };
    });

    App.directive('lightbulb', function() {
      return {
        restrict: 'A',
        require: '^powerSwitch',
        link: function(scope, element, attrs, controller) {
          scope.$watch(function() {
            scope.bulb = controller.getState();
          });
        }
      };
    });

Scope

This is a scary part but do not worry. Setting scope will only create/maintain the hierarchy between the scope of an element and its parent scope but you can still access data bound to parents’ scopes.

scope: false

Is the default option which does not create a new scope for a directive but shares the scope with its parent. In this basic example to understand scopes, I’ve logged the scope of the directive to the console. You can see that the directive has borrowed the controller’s scope so its parent scope will be $rootScope in this case.

scope: false
scope: false

scope: true

Creates a new scope but prototypically inherits from the parent scope. In below screenshot, the directive has its own scope so that its parent scope will be the controller’s scope, not $rootScope.

scope: true
scope: true

scope: ‘isolate’

Creates an isolated scope which does not prototypically inherit from the parent scope but you can access parent scope using scope.$parent. Again basic demo!

How can I then share the data between isolated scope and its parent scope as scope.$parent is not much useful in case of templates?

Well, isolated scope takes an object/hash which lets you derive properties from the parent scope and bind them to the local scope. There are three ways to do that:

  • @ – binds the value of parent scope property (which always a string) to the local scope. So the value you want to pass in should be wrapped in {{}}. Remember `a` in braces.
  • = – binds parent scope property directly which will be evaluated before being passed in.
  • & – binds an expression or method which will be executed in the context of the scope it belongs.

In this example, I’m prefixing parent property with @, children property with = and shout() method with &.

  <div class='well'>
    Bound to $rootScope: <input type="text" ng-model="parent">
    <div child parent='{{parent}}' shout="shout()"></div>    
  </div>

  <div class='form well' ng-controller="OneCtrl">
    ng-controller="OneCtrl"<br/>
    <input type="text" ng-model="children"><br/>
    I am {{children}} and my grandfather is {{parent}}
    <div child parent="{{parent}}" children="children" shout="shout()"></div>    
  </div>
    var App = angular.module('App', []);

    App.run(function($rootScope) {
      $rootScope.parent = 'ROOT';

      $rootScope.shout = function() {
        alert("I am bound to $rootScope");
      };
    });

    App.directive('child', function() {
      return {
        restrict: 'A',
        scope: {
          parent: '@',
          children: '=',
          shout: '&'
        },
        template: '<div class="btn btn-link" ng-click="shout()">I am a directive and my father is {{parent || "NIL"}} as well as {{children || "NIL"}}</div>'
      };
    });

    App.controller('OneCtrl', function($scope) {
      $scope.children = 'The One';

      $scope.shout = function() {
        alert('I am inside ng-controller');
      };
    });

See this example in action.

Transclude

There may be a time when you want your directive to consume the existing content of an element into a template. Angular not only lets you transclude the DOM but also gives you a control to insert the transcluded DOM wherever you want using ngTransclude directive. This option tells angular to get ready for transclusion by providing transclude linking function available in a compile function of a directive. There are two ways to use transclude option in directives:

transclude: true

Inside a compile function, you can manipulate the DOM with the help of transclude linking function or you can insert the transcluded DOM into the template using ngTransclude directive on any HTML tag. Notice our old school marquee tag:

  <script type='text/ng-template' id='whoiam.html'>
    <div class="thumbnail" style="width: 260px;">
      <div><img ng-src="{{data.owner.avatar_url}}" style="width:100%;"/></div>
      <div class="caption">
        <p><b>Name: </b>{{data.name}}</p>
        <p><b>Homepage: </b>{{data.homepage}}</p>
        <p><b>Watchers: </b>{{data.watchers}}</p>
        <p><b>Forks: </b>{{data.network_count}}</p>
        <marquee ng-transclude></marquee>
      </div>
    </div>
  </script>

Please note that ngTransclude will not work in the template if transclude option is not set. Check out Working Demo.

   App.directive('whoiam', function($http) {
      return {
        restrict: 'A',
        transclude: true,
        templateUrl: 'whoiam.html',
        link: function(scope, element, attrs) {
          $http.get('https://api.github.com/repos/angular/angular.js').success(function(data) {
            scope.data = data;
          });
        }
      };
    });

transclude: ‘element’

This transcludes the entire element and a transclude linking function is introduced in the compile function. You can not have access to scope here because the scope is not yet created. Compile function creates a link function for the directive which has access to scope and transcludeFn lets you touch the cloned element (which was transcluded) for DOM manipulation or make use of data bound to scope in it. For your information, this is used in ng-repeat and ng-switch.

<div transclude-element>I got transcluded <child></child></div>

You can inject transcludeFn() in both compile as well as link functions but I’ve not seen the use of it in a link function yet. Demo

   App.directive('transcludeElement', function() {
      return {
        restrict: 'A',
        transclude: 'element',
        compile: function(tElement, tAttrs, transcludeFn) {
          return function (scope, el, tAttrs) {
            transcludeFn(scope, function cloneConnectFn(cElement) {
              tElement.after('<h2>I was added during compilation </h2>').after(cElement);
            });
          };
        }
      };
    });

    App.directive('child', function(){
      return {
        restrict: 'E',
        link: function($scope, element, attrs) {
          element.html(' with my child');
        }
      };
    });

Wrap up

I’ve learned a lot of things I was unaware of about AngularJS Directive while writing this post and I hope this will be useful for other lost souls as well. Its funny to say that it seems like AngularJS source code is itself written in Angular.

If you found this article useful in anyway, feel free to donate me and receive my dilettante painting as a token of appreciation for your donation.

Writing Testable Application in AngularJS – Part 5


Throughout the series, we’ve seen how powerful AngularJS is and how quickly you can write unit tests for your application. In this post, we’ll take a look at grunt .

To create a build, simply execute:

$ grunt build

Above command does a lot of things such as run all tests, compile sass/compass files, minification of css and js, etc.

In our case, we’ve to do slight modifications in Gruntfile.js. If you want to include directories/files which were not created by the generator but to be needed in dist then just locate copy option in Gruntfile.js and add it in src array.

    copy: {
      dist: {
        files: [{
          .
          .
          .
          .
          src: [
            '*.{ico,txt}',
            '.htaccess',
            'components/**/*',
            'images/{,*/}*.{gif,webp}',
            'styles/fonts/*',
            // including data/ directory
            'data/*'
          ]
        }]
      }
    }

And at the bottom under grunt.registerTask('build') option, you can comment/remove tasks to be omitted during the build.

  grunt.registerTask('build', [
    'clean:dist',
    // Do not require below tasks
    // 'jshint',
    // 'test',
    // 'coffee',
    'compass:dist',
    'useminPrepare',
    'imagemin',
    'cssmin',
    'htmlmin',
    'concat',
    'copy',
    'cdnify',
    'ngmin',
    'uglify',
    'rev',
    'usemin'
  ]);

Grab source code

I really enjoyed writing this series and hope you like it. You can grab the source code from github. or checkout the working demo.

If you found this article useful in anyway, feel free to donate me and receive my dilettante painting as a token of appreciation for your donation.

Writing Testable Application in AngularJS – Part 4


In the last post, we’ve seen how to leverage services in Angular to avoid duplicating code and how easy it is to test them. At the moment, our template does not interact with the player. In FIB template created previously, the submit button should get enabled once all inputs are filled, tryagain button will be activated after submitting, and if all 3 attempts lapse, we’ll enable showans button.

As you can see that footer bar does/can not come under FibCtrl so a hackish approach for this problem is to define Boolean variables on the $rootScope to toggle those buttons. Off-course It’ll work but its not elegant because all variables will be in global state.

Getting out of $rootScope hell

A better approach for such problem is to create a custom service and inject it into a controller. You can even share data between controllers.
Lets create a new service named checker. As usual, following command would generate checker.js and its test file.

$ yo angular:service checker

Then we’ll modify it as:

angular.module('eShellApp').factory('checker', function ($location) {
  return {
    // Enable/Disable submit button
    isSubmit: false,

    // Enable/Disable try again button
    isTryagain: false,

    // Enable/Disable show answer button
    isShowans: false,

    // Track attempt. Should increase on every try
    attempt: 1,

    // Max attempts
    totalAttempts: 3,

    // Click handler for submit button
    submit: function () { },

    // Click handler for try again button
    tryagain: function () { },

    // Click handler for show answer button
    showAns: function () { },

    // Getter for all above properties/methods
    getProperty: function(property) {
      return this[property];
    },

    // Setter for all above properties/methods
    setProperty: function(property, value) {
      this[property] = value;
    },

    // Invoke click handlers submit()/tryagain()/showans() after clicking buttons
    invoke: function(property) {
      this[property]();
    },

    // reset all per template, will be called from controllers
    reset: function($scope) {
      this.setProperty('isSubmit', false);
      this.setProperty('isTryagain', false);
      this.setProperty('isShowans', false);
      this.setProperty('submit', $scope['submit']);
      this.setProperty('tryagain', $scope['tryagain']);
      this.setProperty('showans', $scope['showans']);
      this.setProperty('attempt', 1);
      this.setProperty('currentTemplate', parseInt($location.path().substring(1), false));
    }
  };
});

We’ll quickly test the above service to make it future proof. Here jamine’s spyOn method creates a spy for checker.submit() method. And when we call checker.invoke(‘submit’), it calls checker.submit() from within. Finally we just have to check whether checker.submit() has been called or not.

describe('Service: checker', function () {

  // load the service's module
  beforeEach(module('eShellApp'));

  // instantiate service
  var checker;
  beforeEach(inject(function (_checker_) {
    checker = _checker_;
  }));

  it('should get and set properties', function () {
    expect(checker.getProperty('isSubmit')).toBeFalsy();
    checker.setProperty('isSubmit', true);
    expect(checker.getProperty('isSubmit')).toBeTruthy();
  });

  it('should call method properties', function() {
    spyOn(checker, 'submit').andCallFake(function() { });
    checker.invoke('submit');
    expect(checker.submit).toHaveBeenCalled();
  });
});

Creating a controller without route

Finally we’ll create a new controller named footer which will consume checker service.

$ yo angular:controller footer

lets modify footer.js. isEnable() method enables/disables buttons. invoke() method calls checker.invoke() which then call methods defined in the template controller.

angular.module('eShellApp').controller('FooterCtrl', function ($scope, checker) {
  $scope.isEnable = function(button) {
    return checker.getProperty(button);
  };

  $scope.invoke = function(button) {
    checker.invoke(button);
  };

  $scope.attempt = function() {
    return checker.getProperty('attempt');
  };

  $scope.totalAttempts = function() {
    return checker.getProperty('totalAttempts');
  };
});

We’ll update footer bar in index.html to use above methods. ng-disabled enables/disables a button based on expression. ng-click lets you attach click handler the natural way.

<div class="navbar navbar-inverse navbar-fixed-bottom ng-cloak" ng-controller="FooterCtrl">
  <div class="navbar-inner">
    <div class="container">
      <div class="btn-group pull-left">
        <button class="btn btn-inverse input-small" value="Attempt {{attempt()}} of {{totalAttempts()}}" disabled></button>
      </div>

      <div class="btn-group pull-left">
        <button class="btn btn-primary" type="button" ng-disabled="!isEnable('isSubmit')" ng-click="invoke('submit')">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Submit&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</button>
        <button class="btn btn-warning" type="button" ng-disabled="!isEnable('isTryagain')" ng-click="invoke('tryagain')">&nbsp;&nbsp;&nbsp;Try Again&nbsp;&nbsp;&nbsp;</button>
        <button class="btn btn-success" type="button" ng-disabled="!isEnable('isShowans')" ng-click="invoke('showans')">Show Answer</button>
      </div>
      <div class="btn-group pull-right">
        <button class="btn">&laquo;</button>
        <button class="btn">1 of 4</button>
        <button class="btn">&raquo;</button>
      </div>
    </div>
  </div>
</div>

You may remember that we’d attached validate() method on ng-change to all input boxes in FIB template to toggle submit button. Lets define that method:

$scope.validate = function(isValid) {
   checker.setProperty('isSubmit', isValid);
};

Now define click handlers for submit, tryagain and show answer buttons. In submit handler, we just validate input boxes and make them readonly. For example, every input box has input_0 as model, input_0_valid tracks the validity of the model and input_0_readonly toggles readonly html attribute on input boxes. Try again and show answer handlers remove invalid and show valid answers respectively.

  eywa.worship('data/fib.json').then(function(data) {
    $scope.data = data.data;
  });

  $scope.validate = function(isValid) {
    checker.setProperty('isSubmit', isValid);
  };

  $scope.setClass = function(param) {
    if (!angular.isDefined(param)) return '';
    return param === true ? 'success' : 'error';
  };

  $scope.submit = function() {
    var isValid,
        totalValid = 0,
        attemptsLapse = checker.getProperty('attempt') >= checker.getProperty('totalAttempts');

    $scope.data.choices.forEach(function(choice, index) {
      isValid = choice.val2.split(',').indexOf($scope['input_' + index]) !== -1;

      if (isValid) totalValid++;

      $scope['input_' + index + '_valid'] = isValid;
      $scope['input_' + index + '_readonly'] = true;
    });

    checker.setProperty('isTryagain', totalValid !== $scope.data.choices.length && !attemptsLapse);
    checker.setProperty('isShowans', totalValid !== $scope.data.choices.length && attemptsLapse);
    checker.setProperty('isSubmit', false);
  };

  $scope.tryagain = function() {
    $scope.data.choices.forEach(function(choice, index) {
      if (!$scope['input_' + index + '_valid']) {
        $scope['input_' + index + '_valid'] = undefined;
        $scope['input_' + index + '_readonly'] = false;
        $scope['input_' + index] = '';
      }
    });
    checker.setProperty('isTryagain', false);
    checker.setProperty('attempt', checker.getProperty('attempt') + 1);
  };

  $scope.showans = function() {
    $scope.data.choices.forEach(function(choice, index) {
      if (!$scope['input_' + index + '_valid']) {
        $scope['input_' + index + '_valid'] = true;
        $scope['input_' + index + '_readonly'] = true;
        $scope['input_' + index] = choice.val2.split(',')[0];
      }
    });
    checker.setProperty('isShowans', false);
  };

  // Reset footer bar on Template load
  checker.reset($scope);

checker.reset() method simply sets attempt to 1, disables all buttons, etc. when a template loads.
If an input has a valid model value, it will be highlighted in green or in red otherwise. For that purpose, we’ve setClass() method defined above and need to use in ng-class directive inside parseBracket.js directive.

angular.module('eShellApp').directive('parseBracket', function ($compile) {
  return {
    .
    .
    new RegExp(choice.val1.replace(/\[/g, '\\[').replace(/\]/g, '\\]'), 'g'),
    '<span class="control-group" ng-class="setClass(input_' + index + '_valid)">' +
    '<input type="text" ' +
      'class="input-medium" ' +
      'autocorrect="off" ' +
      'autocapitalize="off" ' +
      'autocomplete="off" ' +
      'ng-change="validate(frmFIB.$valid)" ' +
      'ng-readonly="input_' + index + '_readonly" ' +
      'ng-model="input_' + index + '"' +
      'required />' +
     '</span>'
    .
    .
  };
});

As you could see in checker.js service, we’d already defined currentTemplate and totalTemplates variables for a reference. Now in footer.js, we’ll add 4 more methods:

App.controller('FooterCtrl', function ($scope, checker, $location) {
  $scope.currentTemplate = function() {
    return checker.getProperty('currentTemplate');
  };

  $scope.totalTemplates = function() {
    return checker.getProperty('totalTemplates');
  };

  $scope.prevTemplate = function() {
    $location.path('/' + (checker.getProperty('currentTemplate') - 1));
  };

  $scope.nextTemplate = function() {
    $location.path('/' + (checker.getProperty('currentTemplate') + 1));
  };
});

The $location service parses the URL in the browser address bar and makes the URL available to your application.

And update index.html:

   <div class="navbar navbar-inverse navbar-fixed-bottom ng-cloak" ng-controller="FooterCtrl">
      <div class="navbar-inner">
        <div class="container">
          .
          .
          .
          .   
          <div class="btn-group pull-right">
            <button class="btn" ng-disabled="currentTemplate() == 1" ng-click="prevTemplate()">&laquo;</button>
            <button class="btn" ng-bind-template="{{currentTemplate()}} of {{totalTemplates()}}">? of ?</button>
            <button class="btn" ng-disabled="currentTemplate() == totalTemplates()" ng-click="nextTemplate()">&raquo;</button>
          </div>
        </div>
      </div>
    </div>

Here, ng-bind-template directive specifies that the element text should be replaced with the template provided. Unlike ngBind the ngBindTemplate can contain multiple {{ }} expressions.

Wrap up

In the next post, we’ll see how to make the application production ready using grunt build.. Checkout Part 5.

If you found this article useful in anyway, feel free to donate me and receive my dilettante painting as a token of appreciation for your donation.

Writing Testable Application in AngularJS – Part 3


In the last post, we’d used $http service inside a controller to read JSON data which will be quite redundant once we’ll have more templates. So to avoid the redundant code, we’ll abstract it into a custom service which then can be injected in any controller.

Creating a Service

Fire up a terminal and run following command:

$ yo angular:service eywa

We’ve to modify the existing code for eywa service to suit our need. The function worship() takes a JSON file url to read the data from and returns a promise. Notice that we’ll need $http service to be injected here now.

angular.module('eShellApp').factory('eywa', function ($http) {
  return {
    worship: function (request) {
      return $http.get(request);
    }
  };
});

Now we’ll update our controller by injecting eywa and removing $http service from FibCtrl. You can see that nothing has changed but the code is now more robust and testable.

App.controller('FibCtrl', function ($scope, eywa) {
  eywa.worship('data/fib.json').then(function(data) {
    $scope.data = data.data;
  });
});

Testing a Service

In test directory, you may find a test file for the service automatically generated by yeoman generator. What is happening here is that $injector will strip leading and trailing underscores when inspecting function’s arguments to retrieve dependencies. This is useful trick since we can save the variable name without underscores for the test itself. This way we can keep a reference of the service, to use in the test, using the variable named exactly like a service in the $injector.

You can see that the $httpBackend mock allows us to specify expected requests and prepare fake responses. By using the flush() method we can simulate a HTTP response arriving from a back-end at a chosen moment. The verifyNoOutstandingExpectation method verifies that all the expected calls were made ($http methods invoked and responses flushed) while the verifyNoOutstandingRequest call makes sure that code under test didn’t trigger any unexpected XHR calls. Using those 2 methods we can make sure that the code under test invoked all the expected methods and only the expected ones. (Shamelessly copied from AngularJS Web Application Development)

describe('Service: eywa', function () {

  // load the service's module
  beforeEach(module('eShellApp'));

  // instantiate service
  var eywa, $httpBackend;
  beforeEach(inject(function (_eywa_, _$httpBackend_) {
    eywa = _eywa_;
    $httpBackend = _$httpBackend_;
  }));

  afterEach(function() {
    $httpBackend.verifyNoOutstandingExpectation();
    $httpBackend.verifyNoOutstandingRequest();
  });

  it('should read JSON data', function () {
    $httpBackend.whenGET('data/fib.json').respond('{"text":"AngularJS is [[1]].", "choices":[{"val1":"[[1]]", "val2":"awesome,powerful"}]}');

    eywa.worship('data/fib.json').success(function(data) {
      expect(data.text).toBe('AngularJS is [[1]].');
    });
    $httpBackend.flush();
  });
});

beforeEach() and afterEach() execute before and after every it() statement respectively. Notice that the request url passed to worship() method should match up with the one in whenGet().

Wrap up

In the next post, we’ll make all buttons from footer bar interact with the template. Checkout Part 4.

If you found this article useful in anyway, feel free to donate me and receive my dilettante painting as a token of appreciation for your donation.

Writing Testable Application in AngularJS – Part 2


In the last post, we’ve setup yeoman and karma, and then scaffold our application. If you look at app/app.js which currently contains the routing information. Yeoman generator has automagically mapped #/main route and created its associated files mainly main.js and main.html – which we do not need for this application.

We’ll do some cleanup first.

Cleanup

In index.html, remove the unnecessary Google Analytics code and main.js included. Delete following files:

$ cd eShell
$ rm app/scripts/controllers/main.js app/views/main.html test/spec/controllers/main.js

And finally replace

<div class="container"></div>

with

<div class="navbar navbar-inverse navbar-fixed-top">
  <div class="navbar-inner">
    <div class="container"><a class="brand" href="#">eShell - A player for eLearning Courses</a></div>
  </div>
</div>
<div style="margin-top: 40px;"><div ng-view></div></div>
<div class="navbar navbar-inverse navbar-fixed-bottom ng-cloak">
  <div class="navbar-inner">
    <div class="container">
      <div class="btn-group pull-left"><button class="btn btn-inverse input-small" type="text" disabled="disabled" value="Attempt 1 of 3"></button></div>
      <div class="btn-group pull-left">
        <button class="btn btn-primary" type="button">     Submit      </button>
        <button class="btn btn-warning" type="button">   Try Again   </button>
        <button class="btn btn-success" type="button">Show Answer</button>
      </div>
      <div class="btn-group pull-right">
        <button class="btn">«</button>
        <button class="btn">1 of 4</button>
        <button class="btn">»</button>
      </div>
    </div>
  </div>
</div>

First Route

Using yeoman, it takes a split second to create a new route. Just run the following command in a Terminal:

$ yo angular:route fib
create app/scripts/controllers/fib.js
create test/spec/controllers/fib.js
create app/views/fib.html

As you could see, this has created 3 files, a controller, a view and a test. If you look at app.js now, you will see a new route information has automagically been inserted without breaking a thing. Love it!

Lets now update our app.js from:

angular.module('eShellApp', []).config(function ($routeProvider) {
  $routeProvider
    .when('/', {
    templateUrl: 'views/main.html',
    controller: 'MainCtrl'
  })
  .when('/fib', {
    templateUrl: 'views/fib.html',
    controller: 'FibCtrl'
  })
  .otherwise({
    redirectTo: '/'
  });
});

to:

angular.module('eShellApp', []).config(function ($routeProvider) {
  $routeProvider
  .when('/1', {
    templateUrl: 'views/fib.html',
    controller: 'FibCtrl'
  })
  .otherwise({
    redirectTo: '/1'
  });
});

Go and check out http://localhost:9000/

Creating our First Template – Fill in the blanks

This template creates a fill in the blank question backed by JSON data including valid answers. To read the JSON file, angular gives a nifty little service, $http that facilitates communication with the remote HTTP servers via the browser’s XMLHttpRequest object or via JSONP. The reason why I prefer to use $http service because it takes single argument (object) and returns a promise with two specific methods, success and error as it is based on Deferred/Promise APIs.

so, lets create fib.json and put data:

$ touch app/data/fib.json
{
  "text":"AngularJS is originally created in [[1]] by [[2]] and [[3]].",
  "choices":[
    {
      "val1":"[[1]]",
      "val2":"2009,09"
    },
    {
      "val1":"[[2]]",
      "val2":"misko,Misko Hevery"
    },
    {
      "val1":"[[3]]",
      "val2":"adam,adam abrons"
    }
  ]
}

Now, lets modify our fib.js controller to read the above JSON data. We’ll first expose $http object to our controller and $compile service to process the data before injecting into a view.

angular.module('eShellApp').controller('FibCtrl', function ($scope, $http, $compile) {
  $http.get('data/fib.json').then(function(data) {
    $scope.data = data.data;
    var text = '<div>' + data.data.text + '</div>';

    // A bit complex logic to replace every instance of [] with input boxes
    data.data.choices.forEach(function(choice, index) {
      text = text.replace(
        new RegExp(choice.val1.replace(/\[/g, '\\[').replace(/\]/g, '\\]'), 'g'),
        '<span class="control-group">' +
        ' <input type="text" class="input-medium" ' +
        '   autocorrect="off" ' +
        '   autocapitalize="off" ' +
        '   autocomplete="off" ' +
        '   ng-change="validate(frmFIB.$invalid)" ' +
        '   required ' +
        '   data-index="' + index + '" ' +
        '   ng-model="input_' + index + '" />' +
        '</span>'
      );
    });
    $scope.text = $compile(text)($scope).html();
  });
});

We’re just looping through choices we’ve in JSON and replacing the occurrences of [] with input boxes. Notice that I’ve used ng-change directive that will call validate() method on every keypress to toggle submit button. You may be wondering why have I wrapped data.data.text in a div? That’s because $compile service requires it, otherwise you will get some nasty error. Finally I’ve bound compiled text to $scope in order to use it in a view below.

<div class="contentWrapper">
  <div class="content">
    <h3 class="pagination-centered">Fill in the Blanks</h3>
    <form class="form-inline" name="frmFIB" novalidate="">
      <div class="pagination-centered" ng-bind-html-unsafe="text"></div>
    </form>
  </div>
</div>

Again, ng-bind-html-unsafe evaluates the expression (text, in this case) and put it inside the element it bound to (div, in this case). In addition, it is also helpful to get rid of FOUC or precompiled values issue with angular (where you see {{expr}} before the evaluation, mostly in IE).

Enter the Dragon: Directive

Its very bad to do the DOM manipulation inside controller and directive is the best option Angular provides. Lets create our first directive.

$ yo angular:directive parseBracket
create app/scripts/directives/parseBracket.js
create test/spec/directives/parseBracket.js

Now we’ll move the entire parsing logic into above directive. But before that lets replace ng-bind-html-unsafe we’d used earlier with parse-bracket.

<div class="contentWrapper">
  <div class="content">
    <h3 class="pagination-centered">Fill in the Blanks</h3>
    <form class="form-inline" name="frmFIB" novalidate="">
      <div class="pagination-centered" parse-bracket="{{data}}"></div>
    </form>
  </div>
</div>

We’re sending JSON object (data) being parsed to the directive.

According to the author, Directive is a way to teach HTML a new trick. In this case, I’m restricting the directive to be an attribute only and inside link function, $observe observes the changes of parseBracket attribute which contains interpolation. Rest is same borrowed from the controller ;-). You can learn more about directive on adobe or onehungrymind.

angular.module('eShellApp').directive('parseBracket', function ($compile) {
  return {
    restrict: 'A',
    link: function postLink(scope, element, attrs) {
      attrs.$observe('parseBracket', function(val) {
        var params = angular.fromJson(val),
            text = '<div>' + params.text + '</div>';

        if (angular.isDefined(params.choices)) {
          params.choices.forEach(function(choice, index) {
            text = text.replace(
              new RegExp(choice.val1.replace(/\[/g, '\\[').replace(/\]/g, '\\]'), 'g'),
              '<span class="control-group">' +
                '<input type="text" ' +
                  'class="input-medium" ' +
                  'autocorrect="off" ' +
                  'autocapitalize="off" ' +
                  'autocomplete="off" ' +
                  'ng-change="validate(frmFIB.$valid)" ' +
                  'ng-model="input_' + index + '"' +
                  'required />' +
              '</span>'
            );
          });
        }

        element.html(text);
        $compile(element.contents())(scope);
      });
    }
  };
});

Lets Test ’em

You have noticed that when we created the directive using yeoman generator, it also created the test file for the same. We’re going to modify it a bit to pass through the test.


describe('Directive: parseBracket', function () {
  beforeEach(module('eShellApp'));

  var element, $scope;

  it('should replace [] with input boxes', inject(function ($rootScope, $compile) {
    $scope = $rootScope.$new();
    $scope.data = {
      text    : 'AngularJS is [[1]]',
      choices : [{
        "val1": "[[1]]",
        "val2": "awesome,powerful"
      }]
    };
    element = angular.element('<div parse-bracket="{{data}}"></div>');
    element = $compile(element)($scope);
    $rootScope.$digest();

    expect(element.html()).toBe('<div class="ng-scope">AngularJS is <span class="control-group" ng-class="setClass(input_0_valid)"><input type="text" class="input-medium ng-pristine ng-invalid ng-invalid-required" autocorrect="off" autocapitalize="off" autocomplete="off" ng-change="validate(frmFIB.$valid)" ng-readonly="input_0_readonly" ng-model="input_0" required=""></span></div>');
  }));
});

Wrap up

In a next post, We’ll clean up our controller and write some tests for it too. Checkout Part 3.

If you found this article useful in anyway, feel free to donate me and receive my dilettante painting as a token of appreciation for your donation.

Writing Testable Application in AngularJS – Part 1


This will be a 5-Part series to showcase how easy and fun it is to write an AngularJS application. It is a first article of the series where I’m going to give you an overview of the whole application and the utilities we’ll need to get going.

Application Overview

The application we’ll build is called an eLearning player – a building block for more conventional eLearning courses to be run on created with authoring tools. You can learn more about it on upsidelearning and wikipedia. Such players basically consist of different kinds of templates/activities backed by XML/JSON data (produced by Authoring Tools).

LIVE DEMO

Application Overview
Application Overview

Prerequisites

These are some of the tools we’ll use to make the development more enjoyable and sexy. I’m not going to fill this post with all the installation stuff so visit below sites and have it installed for you.

  • Node.js – Server side Javascript that below tools are based on
  • Yeoman – The Workflow Tool
  • Karma Runner – Spectacular Test Runner for JavaScript

Scaffolding

I assume that you have setup above mentioned tools so lets create a scaffold for our application. Type following commands in a terminal:

$ mkdir eShell && cd eShell
$ yo angular
Error angular 

Invalid namespace: angular (or not yet registered).
 Registered: 4 namespace(s). Run with the `--help` option to list them.

In case you get above error, run following commands:

$ sudo npm install generator-angular
$ sudo npm install generator-karma
$ yo angular
Would you like to include Twitter Bootstrap? (Y/n) Y
If so, would you like to use Twitter Bootstrap for Compass (as opposed to vanilla CSS)? (Y/n) Y
Would you like to include angular-resource.js? (Y/n) Y
Would you like to include angular-cookies.js? (Y/n) n
Would you like to include angular-sanitize.js? (Y/n) Y

Finally, run following command as per the instruction to install the required dependencies.

$ sudo npm install && bower install
$ grunt server

If you see, Allo, Allo in a browser that means we’ve achieved our first target. Good Job!!!

Karma Testrunner Setup

In case you want Karma to suit your need, you can simply edit karma.conf.js and karma-e2e.conf.js. Mainly we need to change a port, runnerPort (if ports are in use) and browsers to run tests against. By default, it’s Chrome.

// Karma configuration

// base path, that will be used to resolve files and exclude
basePath = './';

// enable / disable watching file and executing tests whenever any file changes
autoWatch = true;

Finally, run below command which should capture browsers for testing as per karma.conf and will evaluate all tests on every file save (in your choice of IDE).

$ karma start

Checkout Part 2.

If you found this article useful in anyway, feel free to donate me and receive my dilettante painting as a token of appreciation for your donation.