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


5 comments:

  1. Excellent I love the posts here, very helpful for developers in the their application development....
    Angularjs online Training

    ReplyDelete
  2. nice posts..
    ]
    Big data training .All the basic and get the full knowledge of hadoop.
    Big data training


    ReplyDelete
  3. Big data and data warehousing related information is always updated to me at hadoop online training in hyderabad. Nice insight on the topic refer the details at
    hadoop online training

    ReplyDelete
  4. I have read your blog its very attractive and impressive. I like it your blog.

    Angularjs Online Training Angularjs Training Angularjs Training Angularjs Training in Chennai Angularjs Training in Chennai

    ReplyDelete