AngularJS Tutorial: Demystifying Custom Directives – Shared Tutorial
What is a directive? To put it simply, directives are JavaScript functions that manipulate and add behaviors to HTML DOM elements. Directives can be very simplistic or extremely complicated. Therefore, getting a solid grasp on their many options and functions that manipulate them is critical.
In this tutorial, the four functions that execute as a directive is created and applied to the DOM will be explored and examples will be provided. This post assumes some familiarity with AngularJS and custom directives. If you’re newer to Angular, you might enjoy a tutorial on building your first AngularJS app.
The Four Functions of the AngularJS Directive Life Cycle
There are many options that can be configured and how those options are related to each other is important. Each directive undergoes something similar to a life cycle as AngularJS compiles and links the DOM. The directive lifecycle begins and ends within the AngularJS bootstrapping process, before the page is rendered. In a directive’s life cycle, there are four distinct functions that can execute if they are defined. Each enables the developer to control and customize the directive at different points of the life cycle.
The four functions are: compile, controller, pre-link and post-Link.
The compile function allows the directive to manipulate the DOM before it is compiled and linked thereby allowing it to add/remove/change directives, as well as, add/remove/change other DOM elements.
The controller function facilitates directive communication. Sibling and child directives can request the controller of their siblings and parents to communicate information.
The pre-link function allows for private $scope manipulation before the post-link process begins.
The post-link method is the primary workhorse method of the directive.
In the directive, post-compilation DOM manipulation takes place, event handlers are configured, and so are watches and other things. In the declaration of the directive, the four functions are defined like this.
.directive("directiveName",function () { return { controller: function() { // controller code here... }, compile: { // compile code here... return { pre: function() { // pre-link code here... }, post: function() { // post-link code here... } }; } } })
Commonly, not all of the functions are needed. In most circumstances, developers will simply create acontroller and post-link function following the pattern below.
.directive("directiveName",function () { return { controller: function() { // controller code here... }, link: function() { // post-link code here... } } })
In this configuration, link refers to the post-link function.
Whether all or some of the functions are defined, their execution order is important, especially their execution relative to the rest of the AngularJS application.
AngularJS Directive Function Execution Relative to other Directives
Consider the following HTML snippet with the directives parentDir, childDir and grandChildDir applied to the HTML fragment.
<div parentDir> <div childDir> <div grandChildDir> </div> </div> </div>
The execution order of the functions within a directive, and relative to other directives, is as follows:
- Compile Phase
- Compile Function: parentDir
- Compile Function: childDir
- Compile Function: grandChildDir
- Controller & Pre-Link Phase
- Controller Function: parentDir
- Pre-Link Function: parentDir
- Controller Function: childDir
- Pre-Link Function: childDir
- Controller Function: grandChildDir
- Pre-Link Function: grandChildDir
- Post-Link Phase
- Post-Link Function: grandChildDir
- Post-Link Function: childDir
- Post-Link Function: parentDir
AngularJS Directive Function Explanation: Deep Dive
The compilation phase occurs first. Essentially, the compile phase attaches event listeners to the DOM elements. For example, if a particular DOM element is bound to a $scope property, the event listener that allows it to be updated with the the value of the $scope property is applied to the DOM element. The process of compilation starts with the root DOM element from which the AngularJS application was bootstrapped and traverses down the branches of the DOM using a depth-first traversal, compiling a parent first then its children all the way down to the leaf nodes.
Once compilation is complete, directives can no longer be added or removed from the DOM (although there is way around this by directly using the compile service. The next phase is the calling of controllers and pre-link functions for all directives. When the controller is called, the $scope is available and can be used. The$element injected into the controller contains the compiled template but does not include the transcluded child content (transcluded content is the content between the start and end HTML tags on which the directive is applied). By definition, controllers in an MVC pattern simply pass the model to the view and define functions for handling events. Therefore, the controller of a directive should not modify the DOM of the directive for two reasons: it violates the purpose of the controller, and the transcluded child content has not been added to the DOM. So what does a controller do beyond modify the $scope? The controller allows for child directives to communicate with parent directives. The controller function itself should be thought of as a controller object that will be passed into the child directive’s post-link function if the child directive requests it. Therefore, the controller is typically used to facilitate directive communication by creating an object with properties and methods that can be used by its sibling and child directives. The parent directive cannot determine whether a child directive can require its controller, so it is best to limit code in this method to functions and properties that can safely be used by child directives.
After the controller function, the pre-link function executes. The pre-link function is mysterious to a lot of people. If you read much of the documentation on the Internet and in books, people write that this function is used only in rare circumstances and people will almost never need it. Those same explanations then fail to give an example of a situation where it could be used.
The pre-link function is really not complicated at all. First, if you review the AngularJS source code you will find an excellent example of the pre-link function: the directive ng-init uses it. Why? It’s simply a great method to execute private code involving the $scope; code that cannot be called by sibling and child directives. Unlike the controller function, the pre-link function is not passed into directives. Therefore, it can be used to execute code that modifies the $scope of its directive. The directive ng-init does exactly this. When the pre-link function for ng-init executes, it simply executes the JavaScript passed into the directive against the directive’s $scope. The result of the execution is available through the $scope’s prototypal inheritance to child directives during their controller, pre-link and post-link function executions but without giving access to those child directives to re-execute the code in the parent’s pre-link function. Also, the directive may need to execute other code not related to the $scope that it wishes to keep private.
Some experienced AngularJS developers would say this private code could still be placed in the controller and then not called by the child directives. That argument would hold true if the directive will only be used by the original developer who coded it but if the directive is going to be distributed and reused by other developers then encapsulating private code in the pre-link function could be very beneficial. Since developers never know how their directive will be re-used over the course of time, protecting private code from being executed by a child directive is a good approach to directive code encapsulation. I consider it to be a good practice to place directive communication public code in the controller function, and private code in the pre-link function. Like the controller, the pre-link should never do DOM manipulation nor execute a transclude function, since the content for child directives has not been linked yet.
For each directive, its controller and pre-link function executes before the controller and pre-link function of its child directives. Once the controller and pre-link phase for all directives is complete then AngularJS begins the linking phase, and executes the post-link functions for each directive. The linking phase runs opposite to the compile, controller and pre-link execution flows by starting with the leaf DOM nodes and working its way up to the root DOM node. The post-link DOM traversal follows a mostly depth-first path. As each child directive is linked, its post-link function is executed.
The post-link function is the function most commonly implemented in custom AngularJS directives. In this function, almost anything reasonable can be done. The DOM can be manipulated (for itself and child elements only), the $scope is available, the controller object for parent directives can be used, transclude functions can be run, etc. However, there are a few limitations. New directives cannot be added to the DOM because they will not be compiled. Additionally, all DOM manipulations must be done using DOM functions. Simply calling the html function on the DOM element and passing in new HTML will remove all of the event handlers added during the compile process. For example, these will not work as expected:
element.html(element.html());
or
element.html(element.html() + "<div>new content</div>");
The code will not cause the HTML to change, but reassigning the string version of the DOM elements will remove all of the event handlers created during the compile process. Typically, the post-link function is used to wire up event handlers, $watches and $observes.
Once all of the post-link functions are executed, the $scope is applied to the compiled and linked DOM structure, and the AngularJS page comes alive.
Source: https://www.toptal.com/[/pt_text]
[/pt_text]