Angular $ compile source code analysis, angularcompile

Source: Internet
Author: User

Angular $ compile source code analysis, angularcompile

$ Compile is the "compile" service in Angular. It involves two phases: "compile" and "Link" for Angular applications, traverse Angular root nodes (ng-app) and constructed \ $ rootScope objects from the DOM tree, parse root node descendants in sequence, and search for commands based on multiple conditions, complete the operations related to each instruction (such as the instruction scope, controller binding, and transclude), and finally return the linked functions of each instruction, and combine the link functions of all commands into a processed link function, return it to the bootstrap module of Angluar, and finally start the entire application.

[TOC]

Angular compileProvider

Aside from Angular's MVVM implementation method, Angular brings a software engineering concept to the front-end-dependency injection DI. Dependency injection is always the implementation mechanism in the backend field, especially the spring framework of javaEE. The advantage of dependency injection is that developers do not need to manually create an object, which reduces maintenance operations for developers and frees developers from paying attention to object operations related to business logic. So in the front-end field, what is the difference between using dependency injection and previous development?

In my opinion, dependency injection in the front-end field greatly reduces the use of namespaces, such as The namespace reference method of the famous YUI framework, in extreme cases, the object reference may be very long. The injection method consumes only a local variable, and the benefits are naturally visible. In addition, developers only need the name of the related "service" object, and do not need to know the specific reference method of the service. In this way, developers are fully concentrated on the object's pretext reference, focus on business logic development, avoiding repeated searches for relevant documents.

There is a lot of nonsense above, mainly paving the way for the subsequent introduction. In Angular, the method of dependency injection object depends on the Provider of the object. Just like compileProvider in the summary title, this object provides the compile service, which can be passed through injector. invoke (compileProvider. $ get, compileProvider) function to obtain the compile service. Therefore, the problem is transferred to the specific implementation of compileProvider. \ $ get.

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

The code above uses dependency injection to inject five services: \ $ injector, \ $ parse, \ $ controller, \ $ rootScope, \ $ http, \ $ interpolate, they are used to implement "dependency injection injector (\ $ injector), js Code Parser (\ $ parse), controller Service (\ $ controller), root scope (\ $ rootScope ), http service and Command Parsing service ". CompileProvider completes the process from parsing the abstract syntax tree to building the DOM tree through these service tickets, binding the scope, and finally returning the merged link function, enabling Angular applications.

The \ $ get method returns the compile function, which is the specific implementation of the \ $ compile service. The following describes the compile function:

function compile(\$compileNodes, maxPriority) {   var compositeLinkFn = compileNodes(\$compileNodes, maxPriority);   return function publicLinkFn(scope, cloneAttachFn, options) {    options = options || {};    var parentBoundTranscludeFn = options.parentBoundTranscludeFn;    var transcludeControllers = options.transcludeControllers;    if (parentBoundTranscludeFn && parentBoundTranscludeFn.$$boundTransclude) {     parentBoundTranscludeFn = parentBoundTranscludeFn.$$boundTransclude;    }    var $linkNodes;    if (cloneAttachFn) {     $linkNodes = $compileNodes.clone();     cloneAttachFn($linkNodes, scope);    } else {     $linkNodes = $compileNodes;    }    _.forEach(transcludeControllers, function(controller, name) {     $linkNodes.data('$' + name + 'Controller', controller.instance);    });    $linkNodes.data('$scope', scope);    compositeLinkFn(scope, $linkNodes, parentBoundTranscludeFn);    return $linkNodes;   };  }

First, use the compileNodes function to start the root node to be traversed, parse the command, generate the merged link function, and return a publicLinkFn function, this function binds the root node to the root scope, caches the instruction controller instance in the root node, and finally executes the synthesis link function.

Synthesis of link functions

From the previous summary, we can see that the core of the \ $ compile service is the execution of the compileNodes function and the execution of the synthesis link function returned by it. Next, we will go deep 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 = collectdireves VES ($ 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]. childNodes. 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}) ;}); // function 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) {childs.pdf = scope. $ new (); $ (node ). data ('$ scope', childsdsc);} else {childSc Ope = scope;} var boundTranscludeFn; if (linkFn. nodeLinkFn. Equals) {boundTranscludeFn = function (transcludedsfn, cloneAttachFn, transcludeControllers, containingScope) {if (! Transcludedsdetail) {transcludedsdetail = scope. $ new (false, containingScope);} var didTransclude = linkFn. nodeLinkFn. transclude (transcludedScope, cloneAttachFn, {transcludeControllers: transcludeControllers, parentBoundTranscludeFn: parentBoundTranscludeFn}); if (didTransclude. length = 0 & amp; duration) {didTransclude = downlink (transcludedScope, cloneAttachFn) ;}return didTransclude ;};} else if (response) {boundTranscludeFn = callback;} linkFn. nodeLinkFn (linkFn. childLinkFn, childsfn, node, boundTranscludeFn);} else {linkFn. childLinkFn (scope, node. childNodes, parentBoundTranscludeFn) ;}} return compositeLinkFn ;}

The Code is a little long. We analyze it at 1.1 points.

First, the linkFns array is used to store the linked functions after processing all the commands on each DOM node and the linked functions after processing all the commands on the child node. It is implemented in recursion. Then, in the returned compositeLinkFn, it is to traverse linkFns and create corresponding scope objects for each link function (for instructions on creating isolation scopes, create isolation scope objects, and stored in the node cache), and whether the transclude attribute is set in the Processing Command, generate the relevant transclude processing function, and finally execute the link function. If the current command does not have a link function, call the link function of its child elements to process the current element.

In specific implementation, the collectDirectives function is used to scan the commands of all nodes. It processes nodes according to specific rules based on the node type (element node, comment node, and text node). For element nodes, the label name of the current element is stored as an instruction by default, scan the attributes of the element and the CSS class name to determine whether the command definition is satisfied.

Then, execute the applyDirectivesToNode function, Execute Command-related operations, and return the processed link function. It can be seen that applyDirectivesToNode is the core of the \ $ compile service, with the highest priority!

ApplyDirectivesToNode Function

The applyDirectivesToNode function is too complex, so the problem is described only by simple code.
As mentioned above, user-defined commands are executed in this function.

The first step is to initialize relevant attributes. By traversing all the commands of the node, you can determine the $ start attribute, priority, isolation scope, Controller, and transclude attribute for each command in sequence and compile its template, construct the DOM structure of the element, finally execute the User-Defined compile function, add the generated link function to the preLinkFns and postLinkFns arrays, and finally determine whether to recursion its sub-element commands Based on the terminal attribute of the command, complete the same operation.

Specifically, the transclude processing for commands 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 transclude attribute of the command is set to the string "element", the current element node is replaced with the comment, and the original DOM node is re-compiled, if transclude is set to true by default, the child nodes will be compiled, and the compiled DOM object will be passed through transcludeFn to complete custom DOM processing.

In the returned nodeLinkFn, according to the definition of the user instruction, if the instruction has an isolation scope, create an isolation scope and bind the ng-isolate-scope class name to the current dom node, meanwhile, the isolation scope is cached on the dom node;

Next, if a command on the dom node defines the controller, the \ $ cotroller service will be called through the dependency injection method (\ $ injector. obtain the instance of the controller and cache the Controller instance;
Then, call initializeDirectiveBindings to complete one-way binding (@), two-way binding (=), and function reference (&) of the isolation scope attribute. The two-way binding mode for the isolation scope (=) is to compile the simple Angular syntax through a custom compiler, get the value of the expression (identifier) in the specified scope, and save it as lastValue, add parentValueFunction to the $ watch array of the current scope, and execute the \ $ digest loop each time to determine whether the two-way binding attribute is dirty (dirty) to complete value synchronization.

Finally, according to the initialization operation in step 1 of applyDirectivesToNode, The preLinkFns and postLinkFns arrays returned by the traversal execution command compile function are constructed and executed in sequence, as shown below:

_.forEach(preLinkFns, function(linkFn) {     linkFn(      linkFn.isolateScope ? isolateScope : 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),      scopeBoundTranscludeFn     );    });

We can see that the preLinkFns function is executed first, and then the link function of the subnode is traversed and executed. The postLinkFns function is executed to complete the execution of the link function of the current dom element. The compile function of the command returns the postLink function by default. You can use the compile function to return an object containing the preLink and postLink functions to set the preLinkFns and postLinkFns arrays. For example, you can perform DOM operations on sub-elements in the preLink, the efficiency is much higher than the execution in postLink because the DOM of the child element is not built during the execution of the preLink function, especially when the child element is a li with multiple items.

End of compile-publicLinkFn

Finally, it is the end of the process. Use compileNodes to return the final synthesis link function of all commands starting from the root node (the node where the ng-app is located), and then execute the function in the publicLinkFn function. In publicLinkFn, bind the root node to the root scope, cache the Controller instance of the Instruction on the root node, and finally execute the synthesis link function to complete the most important compilation of Angular, the link starts two-way binding in the true sense.

Articles you may be interested in:
  • Explanation of the compile and link functions in angularjs commands
  • AngularJS Initialization Process Analysis (Pilot Program)
  • AngularJS Study Notes: TodoMVC Analysis
  • Comparison and analysis of the difference between $ http. post and jQuery. post in AngularJS

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.