Friday, February 21, 2014

Web technology: JSF vs angularjs

I use angularjs in one of my recent project. I had been using JSF since the very beginning, almost 10 years. My conclusion is that angularjs is definitely the way to go.

Learn curve.

Assume that you know javascript/html/dom/jquery concept, the basic set of tool for web development, angularjs definitely wins out. I spent two days over angularjs documentation and starting coding after it. I believe I am an expert after one month. Have question on it? No problem. Test it out in offline html, plunker, or dig into the angular.js source code.

On the other hand, JSF is not so easy. Check out here how many chapters you need to go through before you can start work on it.  There are tons of concepts and API.  Suppose you are good at learning new stuff and ready to go right now, you will be surprised! The build/render concept is subtle and very difficult for beginner. Even experienced developers are bitten by it from time to time. Do a search on JSF build/render. You can find out how many questions are asked.

Reusable UI component

JSF struggles hard to provide facility to author reusable component. First authoring a new UI component is not easy, even right now in jsf 2.2. JSF 2.0 provides composite component and resources. It seems framework is there. Component developers are ready to go.  Developing component to show some text is easy and it always work. Once you are trying to develop some component with form fields, trouble will comes. It works in your test, but not in some situation. From my experience, this is related to component state management by UIData(html datatable or UI repeat). JSF just can not handle the input state component if they are deeply nested in UIData. Go mojarra issues track to search for UIData related issues. You are surprised how many are unresolved, or reopened.
Even the component is for static text display, it is not easy to use. Here is one comparison example. To display a tree in html, I use popular primefaces library. You can find out how many class you need to write at server side to just display the tree.   I did a search before writing up this post for tree component in angularjs. Here is an example.  You just need to supply a tree-style json object, just as I expect, as simple as it.

Buggy

JSF library is buggy, just because its concept is complicated. In lasy two years, I reported around ten bugs in mojarra. I did not find any bug in angularjs core library so far.

Simplicity

Angularjs is simple. It core library is only about 20,000 lines. Most of it is inline documentation. It can be easily understood. On the other hand, JSF is way too complicated.If you do not believe, grab the mojarra source code and try to figure out how it does its trick. Why do you need to read the source? Soon or late, you find your code does not work. You need to understand why it does not. The source comes to rescue at this time. So you need to understand portion of source related to your problem.

Server requirement

JSF: CDI for scope management. EL for data binding. Bean validation for validation.
Angular js: JAX-RS for data. No scope is need. Data-binding is done at client side. Validation can be done easily in client side.

Configuration

JSF: many options.
 angularjs: do we have such thing like configuration?

Case Study

http://blog.flexdms.com/2014/04/a-picklist-component-for-angularjs.html

Thursday, February 20, 2014

angularjs compilation flow

angularjs compilation does two things.
  • parse the html template and collect all directives. 
  • bind directives with scope and produce the final html
All the directives are handled in orderly fashion. The order is established by two rules.
  1. First, establish the parent-child relationship in the DOM. At this stage, we are talking about the template DOM, not the final html. The directives in parent are always handled first.
  2. Directives with higher priority in the same node are handled before those with lower priority.

Compilation

Step 1 :So give  this example in plunker, the normal order will be like this A1(priority 99)->A3 and form(priority 0),->A2(priority -1)->B1 (child). Once the order is established, the compilation is finished.

Link

Step 2 :controller for each directive is called following the established order.  Each controller has its turn to augment the scope.   The scope is fully established after this step.
Step 3: The link function for each directive is called in reverse order.  So here the order is B1->A2->A3 and form->A1.

The example in the plunker logs all controller call and link function call to console. You can turn on the console to view the compilation and link sequencer.

Let us see how we can use this knowledge to tackle one tough problem I face in one of my project. In one of my project, I need to generate dynamic form whose name is determined by scope property. But the ngForm property only accepts literal text instead of an expression for the form name. How can we tweak the compilation/link process to make the form to accept an expression? This question is also asked in stackflow.

Alter the link flow with terminal property

I replaced A2 with another directive A4 following the suggestion in the stackflow section. A41 is terminal. In the link function, it changes/add form name by evaluating an expression.

testApp.directive("testA41", function($compile){
  
    return {
        terminal:true,
        priority:101,
        restrict: 'A',
        controller: function($scope, $element, $attrs){
            if ($element.controller("form")==null){
                console.log("controller: A41-101:NO form ");
            } else {
                console.log("controller: A41-101: YES form");
            }
        },
        link: function(scope, element, attrs){
            if (element.controller("form")==null){
                console.log("\t link:A41-101: No form");
            } else {
                console.log("\t: link A41-101: YES form");
            }
             element.removeAttr("data-test-a41"); //avoid infinite loop
             element.removeAttr("test-a41");
            attrs.$set("name","test"); //set name

            $compile(element)(scope); //recompile
        }
    };
});




 In step 2, the controller is called by the order A41(priority 101)->A1->A3 and form(priority 0)->B1 (child). Since A41 is terminal, A1->A3 and form(priority 0)->B1 (child) is never called. The normal flow is interrupted.
In step 3, only A41 is called, all others are skipped. Inside A4's link function, A4 removes itself from element and use $compile to start a compilation process. Basically A4 asks angularjs re-perform step 1, step2 and  step 3.  But this time, form name is set.

communicate with each other through controller

angularjs suggests directives comunicate with each other through link function. But in our case, we could communicate each other with controller. we just need a controller that alters element's name before ngForm directive is called.  So for this we have new directive A5.

testApp.directive("testA5", function($compile){
   
    return {
        priority:101,
        restrict: 'A',
        controller: function($scope, $element, $attrs){
            if ($element.controller("form")===null){
                console.log("controller: A5-101:NO form ");
            } else {
                console.log("controller: A4-101: YES form");
            }
            $element.removeAttr("data-test-a5"); //do not remove if we have link function
            $element.removeAttr("test-a5");
            $attrs.$set("name","test");
        },
        link: function(scope, element, attrs){
            if (element.controller("form")===null){
                console.log("\t link:A5-101: No form");
            } else {
                console.log("\t: link A5-101: YES form");
            }
        }
    };
});


This new directive move the name change to controller. It has a higher priority than form. So when it comes to form directive, the name is ready. This solution is better than previous one since we do not change any compilation flow


angularjs Controller in detail

What is angularjs controller, here is a quote from angularjs website

In Angular, a Controller is a JavaScript constructor function that is used to augment the Angular Scope.
So it is used to augment scope:
  • add properties to scope
  • initialize scope
  • add behaviors to scope
The controller name is like a semantic class name or tag for the piece of function added to scope
So does it matter it is constructor function or regular function? No. It does not matter in almost all situations. You usually do not use the this keyword in constructor function. After the scope is augmented,  the controller object is forgotten. The controller object created by angularjs from this constructor function is never referred again. It is actual an empty object without any property or function.
But it is actually not empty and used in two cases in angularjs. It is the FormController and ngModelController. These two controllers have a bunch of methods and properties themselves. These methods coordinates with each other to provide validation for inputs.

Get hold reference of  controller object

So how can you find out these controller objects? Required controller can be specified when directive is designed. The needed controller is available for use in link function.
instApp.directive('fxPropeditor', function($compile,fxTemplates){
    return {
        require:"^form", //need to work under form controller
        replace:true,
        template: "<div/>",
        controller : myController,
        link : function(scope, iElement, iAttrs, form, transcludeFn) {

        };
});
     
In the above example, require property tells angularjs a form controller is needed in parent dom. In the link function, the form controller object is available from injection. It can be used to set form validation state or add extra custom form control.

Retrieve controller object afterward.

The form control sets itself to scope like this
$scope.formname=this;
in controller constructor function. The formname is the string literal of name property.  One can find out form controller from scope using formname. But other controllers usually do not add itself scope.  Moreover form controller is not added to scope if there is no name for form. For example, given html like this
<form ID="TEST">
            <input type="text" data-ng-model="test"/>
        </form>

The form controller will not be attached to scope since there is no name attribute for <form>.  Angularjs attaches controller object to DOM node using data() under the hood.  You can always inspect controllers associated with an element using the data().  angular.element(document.getElementById("TEST")).data().  Controller  can be retrieved like this like this angular.element(document.getElementById("TEST")).controller("form").

Controller and Scope

Is there a new scope for every controller object? No. The relation between controller object and scope depends on the directive.  The fxPropeditor directive example above will create a new myController controller object. But no new child scope is created since this directive does not have a scope property. The is also true for form controller.  On the other hand, ngController always creates a child scope.
var ngControllerDirective = [function() {
  return {
    scope: true,
    controller: '@',
    priority: 500
  };
}];

since it explicitly requests a new child scope.