Angular $compile Source Analysis _angularjs

Source: Internet
Author: User

$compile, in angular, the "compile" service, which involves the "compile" and "link" phases of the angular application, based on the angular root node (Ng-app) and the constructed \ $rootScope object from the DOM tree. The descendants of the root node are parsed in sequence, the instructions are searched according to a variety of conditions, and the related operations of each instruction (such as the scope of the instruction, the controller binding and the transclude, etc.) are returned, and the link function of all the instructions is then combined into a processed link function. Return to Angluar's bootstrap module, which eventually starts the entire application.

[TOC]

Angular's Compileprovider

Aside from the MVVM implementation of angular, angular brings a concept of software engineering to the front end-dependency injection di. Dependency injection is never an implementation mechanism in the backend domain, especially the Java EE Spring Framework. The advantage of relying on injection is that it eliminates the need for developers to manually create an object, which reduces the maintenance operations associated with the developer, leaving developers with no need to focus on object operations related to the business logic. So in the front-end field, what is the experience with dependency injection that is different from previous development?

I think that the front-end field of dependency injection, then greatly reduce the use of namespaces, such as the famous Yui framework namespace reference, in extreme cases, the object may be very long reference. In the way of injection, the consumption is only a local variable, the benefits are naturally visible. And developers simply need the name of the relevant "service" object, without knowing how the service is referenced, so that the developer concentrates on the object's pretext, focuses on the development of the business logic, and avoids the repeated search for the relevant document.

In front of a lot of nonsense, mainly for the introduction of the following to do bedding. In angular, the dependency of the injected object depends on the provider of the object, as in the Compileprovider of the summary header, which provides the compile service that is available through Injector.invoke (Compileprovider . $get, Compileprovider) function completes the compile service acquisition. Therefore, the problem is shifted to the concrete implementation of the analysis compileprovider.\ $get.

compileprovider.\ $get

this.\ $get = [' $injector ', ' \ $parse ', ' $controller ', ' \ $rootScope ', ' $http ', ' \$ Interpolate ',
   function (\ $injector, \ $parse, \ $controller, \ $rootScope, \ $http, \ $interpolate) {
 ...
 return compile;
}

The above code uses a dependency injection method to inject \ $injector, \ $parse, \ $controller, \ $rootScope, \ $http, \ $interpolate Five services, respectively, for implementing the "Dependency Injection injection" (\$ INJECTOR), JS code parser (\ $parse), controller service (\ $controller), root scope (\ $rootScope), HTTP service and instruction parsing service. Compileprovider through these services, it completes the parsing from the abstract syntax tree to the DOM tree construction, the scope binding and finally returns the synthesis link function, realizes the angular application to open.

\ $get method ultimately returns the Compile function, the compile function is the specific implementation of the $compile service. Let's drill down to the compile function:

 function compile (\ $compileNodes, maxpriority) {var compositelinkfn = Compilenodes (\ $compileNo

   DES, maxpriority); return function PUBLICLINKFN (scope, CLONEATTACHFN, options) {options = Options | |
    {};
    var parentboundtranscludefn = Options.parentboundtranscludefn;
    var transcludecontrollers = options.transcludecontrollers; if (parentboundtranscludefn && parentboundtranscludefn.$ $boundTransclude) {parentboundtranscludefn = Parentb
    oundtranscludefn.$ $boundTransclude;
    var $linkNodes;
     if (CLONEATTACHFN) {$linkNodes = $compileNodes. Clone ();
    CLONEATTACHFN ($linkNodes, scope);
    else {$linkNodes = $compileNodes; } _.foreach (Transcludecontrollers, function (Controller, name) {$linkNodes. Data (' $ ' + name + ' controller '), contro
    Ller.instance);
    });
    $linkNodes. Data (' $scope ', scope);
    COMPOSITELINKFN (Scope, $linkNodes, parentboundtranscludefn);
   return $linkNodes;
  }; }

First, through the Compilenodes function, for the need to traverse the root node to start, complete the instruction parsing, and generate the composite link function, return a PUBLICLINKFN function, which completes the root node and the root scope of the binding, and the root node cache instruction controller instance, Finally, the composite link function is executed.

Generation of synthetic link functions

By the last summary, we can see that the core of $compile service is the execution of the Compilenodes function and the execution of the synthesized link function returned. Below, we delve into the specific logic of Compilenodes:

function Compilenodes ($compileNodes, maxpriority) {var linkfns = [];
    _.times ($compileNodes. length, function (i) {var attrs = new Attributes ($ ($compileNodes [i]));
    var directives = collectdirectives ($compileNodes [i], attrs, maxpriority);
    var nodelinkfn;
    if (directives.length) {NODELINKFN = Applydirectivestonode (directives, $compileNodes [i], attrs);
    } var Childlinkfn; if (!NODELINKFN | |!nodelinkfn.terminal) && $compileNodes [i].childnodes && $compileNodes [I].childno
    Des.length) {CHILDLINKFN = Compilenodes ($compileNodes [i].childnodes);
    } if (Nodelinkfn && nodelinkfn.scope) {attrs.$ $element. addclass (' Ng-scope ');
      } if (Nodelinkfn | | childlinkfn) {linkfns.push ({nodelinkfn:nodelinkfn, CHILDLINKFN:CHILDLINKFN,
    IDX:I});

   }
   });
    The link function that executes the instruction functions COMPOSITELINKFN (scope, linknodes, parentboundtranscludefn) {var stablenodelist = []; _.foreach (Linkfns, Function (LINKFN) {var nodeidx = Linkfn.idx;
    STABLENODELIST[LINKFN.IDX] = Linknodes[linkfn.idx];

    });
     _.foreach (Linkfns, function (LINKFN) {var node = Stablenodelist[linkfn.idx];
      if (LINKFN.NODELINKFN) {var childscope;
       if (linkFn.nodeLinkFn.scope) {childscope = scope. $new ();
      $ (node). Data (' $scope ', childscope);
      else {childscope = scope;
      } var Boundtranscludefn; if (linkFn.nodeLinkFn.transcludeOnThisElement) {boundtranscludefn = function (Transcludedscope, CLONEATTACHFN, Tran Scludecontrollers, Containingscope) {if (!transcludedscope) {transcludedscope = scope. $new (False, Conta
        Iningscope); var didtransclude = LinkFn.nodeLinkFn.transclude (Transcludedscope, CLONEATTACHFN, {Transcludecontrolle
        Rs:transcludecontrollers, parentboundtranscludefn:parentboundtranscludefn}); if (Didtransclude.length = = 0 && parENTBOUNDTRANSCLUDEFN) {didtransclude = Parentboundtranscludefn (Transcludedscope, CLONEATTACHFN);
       return didtransclude;
      };
      else if (parentboundtranscludefn) {boundtranscludefn = Parentboundtranscludefn;
     } linkfn.nodelinkfn (LINKFN.CHILDLINKFN, Childscope, node, boundtranscludefn);
     else {LINKFN.CHILDLINKFN (scope, node.childnodes, parentboundtranscludefn);
   }
    });
  return COMPOSITELINKFN;

 }

The code is a little long, we analyze 1.1 points.

First, the LINKFNS array is used to store the linked functions after the processing of all instructions on each DOM node and after the processing of all instructions on the child nodes, which are implemented in a recursive way. Subsequently, in the returned COMPOSITELINKFN, the Linkfns is traversed, and for each linked function, the corresponding scope object is created (for instructions to create an isolation scope, an isolated scope object is created and stored in the node's cache), and whether the directive sets the Transclude property, generates the relevant transclude handler function, and finally executes the link function, and if the current instruction does not have a linked function, calls its child element's link function to complete the current element's processing.

In the concrete implementation, the Collectdirectives function is used to complete the instruction scan of all nodes. It will be based on the type of node (element node, annotation node and text node) according to the specific rules, for the element node, the default store the current element of the label name is a directive, while scanning the attributes of elements and CSS class names, to determine whether the instructions to meet the definition.

Immediately thereafter, execute the Applydirectivestonode function, perform the instruction-related operations, and return the processed link function. This shows that Applydirectivestonode is the core of $compile services, the most important!

Applydirectivestonode function

The Applydirectivestonode function is too complex, so only simple code is used to illustrate the problem.
As mentioned above, the operation of the user-defined instruction is performed in the function.

First is the initialization of related properties, by traversing all the instructions of the node, in order to determine the $ $start attribute, priority, isolation scope, controller, transclude attribute to judge and compile its template, build the DOM structure of the element, and finally execute the user-defined compile function. Adds the generated link functions to the PRELINKFNS and Postlinkfns arrays, and ultimately completes the same operation based on the terminal property of the instruction to determine whether to recursively its child element directives.

The transclude processing of instructions requires special instructions:

if (directive.transclude = = ' element ') {
      haselementtranscludedirective = true;
      var $originalCompileNode = $compileNode;
      $compileNode = attrs.$ $element = $ (document.createcomment (' + directive.name + ': ' + attrs[directive.name] + '); 
   
     $originalCompileNode. replacewith ($compileNode);
      terminalpriority = directive.priority;
      Childtranscludefn = Compile ($originalCompileNode, terminalpriority);
     } else {
      var $transcludedNodes = $compileNode. Clone (). contents ();
      Childtranscludefn = compile ($transcludedNodes);
      $compileNode. Empty ();
     

   

If the directive's Transclude property is set to the string "element", the current element node is replaced with a comment comment, and the original DOM node is recompiled, and if Transclude is set to the default true, it continues to compile its child nodes. And through Transcludefn pass the compiled Dom object, completes the user custom DOM processing.

In the returned NODELINKFN, according to the definition of the user directive, if the instruction has an isolation scope, create an isolation scope and bind the Ng-isolate-scope class name to the current DOM node, and cache the isolation scope on the DOM node;

Next, if a directive on a DOM node defines a controller, then the \ $cotroller service is invoked to obtain an instance of the controller by means of a dependency injection (\ $injector. Invoke) and caches the controller instance;
Then, call Initializedirectivebindings, complete the one-way binding (@) of the isolation scope properties, the bidirectional binding (=) and the reference (&) of the function, and the implementation of the bidirectional binding mode (=) for the quarantine scope. is to compile the simple angular syntax through a custom compiler, get the value of the expression (identifier) under the specified scope, save as Lastvalue, and add it to the $watch array of the current scope by setting parentvaluefunction, each time \$ Digest loops to determine whether two-way bound properties are dirty (dirty), and complete the synchronization of values.

Finally, according to the initialization operation of the first step of Applydirectivestonode, the Prelinkfns and postlinkfns arrays of the link functions returned by the traversal execution instruction compile function are constructed and executed sequentially, as follows:

 _.foreach (Prelinkfns, function (LINKFN) {LINKFN (linkfn.isolatescope? isolates)
      Cope:scope, $element, Attrs, Linkfn.require && getcontrollers (Linkfn.require, $element),
    SCOPEBOUNDTRANSCLUDEFN);
    });
     if (CHILDLINKFN) {var scopetochild = scope;
     if (newisolatescopedirective && newisolatescopedirective.template) {scopetochild = Isolatescope;
    } childlinkfn (Scopetochild, Linknode.childnodes, Boundtranscludefn);
      } _.foreachright (Postlinkfns, function (LINKFN) {LINKFN (linkfn.isolatescope? Isolatescope:scope, $element, Attrs, Linkfn.require && getcontrollers (Linkfn.require, $element), Scopeboundtransclud
    EFN);
}); 

As you can see, the function of the PRELINKFNS is executed first, followed by the link function of the child node, and executed, and finally the function of the POSTLINKFNS is executed to complete the link function of the current DOM element. The compile function of the instruction returns the Postlink function by default, and the compile function returns an array of Postlink and Prelinkfns objects that contains the PreLink and POSTLINKFNS functions. If Dom is prelink for child elements, the efficiency is much higher than in Postlink, because the PreLink function does not build the DOM of the child element when it is executed, especially when the child element is an Li with multiple items.

End of COMPILE-PUBLICLINKFN

Finally, it's almost the end of the stage. The final compositing link function of all instructions starting from the root node (NG-APP) is returned by Compilenodes, which is eventually executed in the PUBLICLINKFN function. In Publiclinkfn, it completes the binding of root node and root scope, and the controller instance that caches instruction at the root node, finally executes the synthesis link function, completes the angular most important compilation, links two stages, thus starts the real sense bidirectional binding.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.