Code


<!DOCTYPE html> <html> <head> <script src="angular.js"></script> <script src="script.js"></script> <style> .logTextAreaSmall { width: 400px; height: 80px; } .logTextAreaMedium { width: 400px; height: 150px; } .logTextAreaBig { width: 400px; height: 220px; } .logTextAreaTall { width: 400px; height: 330px; } </style> </head> <body ng-app="mainModule"> <div ng-controller="mainController"> <h3>1. No definition object</h3> <div ngh-no-definition-object="1"></div> <div ngh-no-definition-object="2"></div> <textarea class="logTextAreaSmall">{{noDefObjLog}}</textarea> <br /> <h3>2. Post-link function</h3> <div ngh-postlink-function="1"></div> <div ngh-postlink-function="2"></div> <textarea class="logTextAreaSmall">{{postlinkFunctionLog}}</textarea> <br /> <h3>3. Pre-link and post-link functions</h3> <div ngh-pre-postlink-function="1"></div> <div ngh-pre-postlink-function="2"></div> <textarea class="logTextAreaMedium">{{prePostlinkFunctionLog}}</textarea> <br /> <h3>4. Compile function</h3> <div ngh-compile-function="1"></div> <div ngh-compile-function="2"></div> <textarea class="logTextAreaSmall">{{compileFunctionLog}}</textarea> <br /> <h3>5. Compile and post-link functions</h3> <div ngh-compile-postlink-functions="1"></div> <div ngh-compile-postlink-functions="2"></div> <textarea class="logTextAreaMedium">{{compilePostlinkFunctionsLog}}</textarea> <br /> <h3>6. Compile, pre-link and post-link functions</h3> <div ngh-compile-pre-postlink-functions="1"></div> <div ngh-compile-pre-postlink-functions="2"></div> <textarea class="logTextAreaMedium">{{compilePrePostlinkFunctionsLog}}</textarea> <br /> <h3>7. Nested directives</h3> <div ngh-nested-dir-1> <div ngh-nested-dir-2> <div ngh-nested-dir-3> </div> </div> </div> <textarea class="logTextAreaBig">{{nestedDirectivesLog}}</textarea> <br /> <h3>8. Multiple directives on a DOM node</h3> <div ngh-multi-dir-1 ngh-multi-dir-2 ngh-multi-dir-3></div> <textarea class="logTextAreaBig">{{multiDirectivesLog}}</textarea> <br /> <h3>9. Complete example (nested directives and multiple directives on a DOM node)</h3> <div ngh-dir-1> <div ngh-dir-2 ngh-dir-3> <div ngh-dir-4 ngh-dir-1> </div> </div> <div ngh-dir-5> <div ngh-dir-6> </div> </div> </div> <textarea class="logTextAreaTall">{{completeExampleLog}}</textarea> </div> </body> </html>
// *** LOGGING - START *** // Elements defined outside the module just for logging var logScope = null; var noDefObjLog = ""; var postlinkFunctionLog = ""; var prePostlinkFunctionLog = ""; var compileFunctionLog = ""; var compilePostlinkFunctionsLog = ""; var compilePrePostlinkFunctionsLog = ""; var nestedDirectivesLog = ""; var multiDirectivesLog = ""; var completeExampleLog = ""; var logLine = function (logVariable, directiveName, logString) { if (logScope) { logScope[logVariable] += "[" + directiveName + "] " + logString + "\n"; } else { this[logVariable] += "[" + directiveName + "] " + logString + "\n"; } }; // *** LOGGING - END *** angular.module("mainModule", []) .controller("mainController", function ($scope) { $scope.noDefObjLog = noDefObjLog; $scope.postlinkFunctionLog = postlinkFunctionLog; $scope.prePostlinkFunctionLog = prePostlinkFunctionLog; $scope.compileFunctionLog = compileFunctionLog; $scope.compilePostlinkFunctionsLog = compilePostlinkFunctionsLog; $scope.compilePrePostlinkFunctionsLog = compilePrePostlinkFunctionsLog; $scope.nestedDirectivesLog = nestedDirectivesLog; $scope.multiDirectivesLog = multiDirectivesLog; $scope.completeExampleLog = completeExampleLog; logScope = $scope; }) // 1. Directive without a definition object .directive("nghNoDefinitionObject", function () { // Initialization logLine("noDefObjLog", "nghNoDefinitionObject", "Initialization"); // Post-link function return function (scope, element, attrs) { logLine("noDefObjLog", "nghNoDefinitionObject", "Post-link " + attrs.nghNoDefinitionObject); }; }) // 2. Directive with a definition object and just the post-link function .directive("nghPostlinkFunction", function () { // Initialization logLine("postlinkFunctionLog", "nghPostlinkFunction", "Initialization"); // Definition object return { // Post-link function link: function (scope, element, attrs) { logLine("postlinkFunctionLog", "nghPostlinkFunction", "Post-link " + attrs.nghPostlinkFunction); } }; }) // 3. Directive with a definition object and the pre-link and post-link functions .directive("nghPrePostlinkFunction", function () { // Initialization logLine("prePostlinkFunctionLog", "nghPrePostlinkFunction", "Initialization"); // Definition object return { link: { // Pre-link function pre: function (scope, element, attrs) { logLine("prePostlinkFunctionLog", "nghPrePostlinkFunction", "Pre-link " + attrs.nghPrePostlinkFunction); }, // Post-link function post: function (scope, element, attrs) { logLine("prePostlinkFunctionLog", "nghPrePostlinkFunction", "Post-link " + attrs.nghPrePostlinkFunction); } } }; }) // 4. Directive with a definition object and just the compile function .directive("nghCompileFunction", function () { // Initialization logLine("compileFunctionLog", "nghCompileFunction", "Initialization"); // Definition object return { // Compile function compile: function (element, attrs) { logLine("compileFunctionLog", "nghCompileFunction", "Compile " + attrs.nghCompileFunction); } }; }) // 5. Directive with a definition object and the compile and post-link functions .directive("nghCompilePostlinkFunctions", function () { // Initialization logLine("compilePostlinkFunctionsLog", "nghCompilePostlinkFunctions", "Initialization"); // Definition object return { // Compile function compile: function (element, attrs) { logLine("compilePostlinkFunctionsLog", "nghCompilePostlinkFunctions", "Compile " + attrs.nghCompilePostlinkFunctions); // Post-link function return function (scope, element, attrs) { logLine("compilePostlinkFunctionsLog", "nghCompilePostlinkFunctions", "Post-link " + attrs.nghCompilePostlinkFunctions); }; } }; }) // 6. Directive with a definition object and the compile, pre-link and post-link functions .directive("nghCompilePrePostlinkFunctions", function () { // Initialization logLine("compilePrePostlinkFunctionsLog", "nghCompilePrePostlinkFunctions", "Initialization"); // Definition object return { // Compile function compile: function (element, attrs) { logLine("compilePrePostlinkFunctionsLog", "nghCompilePrePostlinkFunctions", "Compile " + attrs.nghCompilePrePostlinkFunctions); return { // Pre-link function pre: function (scope, element, attrs) { logLine("compilePrePostlinkFunctionsLog", "nghCompilePrePostlinkFunctions", "Pre-link " + attrs.nghCompilePrePostlinkFunctions); }, // Post-link function post: function (scope, element, attrs) { logLine("compilePrePostlinkFunctionsLog", "nghCompilePrePostlinkFunctions", "Post-link " + attrs.nghCompilePrePostlinkFunctions); } }; } }; }) // 7. Nested directives .directive("nghNestedDir1", function () { // Initialization logLine("nestedDirectivesLog", "nghNestedDir1", "Initialization"); // Definition object return { // Compile function compile: function (element, attrs) { logLine("nestedDirectivesLog", "nghNestedDir1", "Compile"); return { // Pre-link function pre: function (scope, element, attrs) { logLine("nestedDirectivesLog", "nghNestedDir1", "Pre-link"); }, // Post-link function post: function (scope, element, attrs) { logLine("nestedDirectivesLog", "nghNestedDir1", "Post-link"); } }; } }; }) .directive("nghNestedDir2", function () { // Initialization logLine("nestedDirectivesLog", "nghNestedDir2", "Initialization"); // Definition object return { // Compile function compile: function (element, attrs) { logLine("nestedDirectivesLog", "nghNestedDir2", "Compile"); return { // Pre-link function pre: function (scope, element, attrs) { logLine("nestedDirectivesLog", "nghNestedDir2", "Pre-link"); }, // Post-link function post: function (scope, element, attrs) { logLine("nestedDirectivesLog", "nghNestedDir2", "Post-link"); } }; } }; }) .directive("nghNestedDir3", function () { // Initialization logLine("nestedDirectivesLog", "nghNestedDir3", "Initialization"); // Definition object return { // Compile function compile: function (element, attrs) { logLine("nestedDirectivesLog", "nghNestedDir3", "Compile"); return { // Pre-link function pre: function (scope, element, attrs) { logLine("nestedDirectivesLog", "nghNestedDir3", "Pre-link"); }, // Post-link function post: function (scope, element, attrs) { logLine("nestedDirectivesLog", "nghNestedDir3", "Post-link"); } }; } }; }) // 8. Multiple directives on a DOM node .directive("nghMultiDir1", function () { // Initialization logLine("multiDirectivesLog", "nghMultiDir1", "Initialization"); // Definition object return { // Compile function compile: function (element, attrs) { logLine("multiDirectivesLog", "nghMultiDir1", "Compile"); return { // Pre-link function pre: function (scope, element, attrs) { logLine("multiDirectivesLog", "nghMultiDir1", "Pre-link"); }, // Post-link function post: function (scope, element, attrs) { logLine("multiDirectivesLog", "nghMultiDir1", "Post-link"); } }; } }; }) .directive("nghMultiDir2", function () { // Initialization logLine("multiDirectivesLog", "nghMultiDir2", "Initialization"); // Definition object return { // Compile function compile: function (element, attrs) { logLine("multiDirectivesLog", "nghMultiDir2", "Compile"); return { // Pre-link function pre: function (scope, element, attrs) { logLine("multiDirectivesLog", "nghMultiDir2", "Pre-link"); }, // Post-link function post: function (scope, element, attrs) { logLine("multiDirectivesLog", "nghMultiDir2", "Post-link"); } }; } }; }) .directive("nghMultiDir3", function () { // Initialization logLine("multiDirectivesLog", "nghMultiDir3", "Initialization"); // Definition object return { // Compile function compile: function (element, attrs) { logLine("multiDirectivesLog", "nghMultiDir3", "Compile"); return { // Pre-link function pre: function (scope, element, attrs) { logLine("multiDirectivesLog", "nghMultiDir3", "Pre-link"); }, // Post-link function post: function (scope, element, attrs) { logLine("multiDirectivesLog", "nghMultiDir3", "Post-link"); } }; } }; }) // 9. Complete example (nested directives and multiple directives on a DOM node) .directive("nghDir1", function () { // Initialization logLine("completeExampleLog", "nghDir1", "Initialization"); // Definition object return { // Compile function compile: function (element, attrs) { logLine("completeExampleLog", "nghDir1", "Compile"); return { // Pre-link function pre: function (scope, element, attrs) { logLine("completeExampleLog", "nghDir1", "Pre-link"); }, // Post-link function post: function (scope, element, attrs) { logLine("completeExampleLog", "nghDir1", "Post-link"); } }; } }; }) .directive("nghDir2", function () { // Initialization logLine("completeExampleLog", "nghDir2", "Initialization"); // Definition object return { // Compile function compile: function (element, attrs) { logLine("completeExampleLog", "nghDir2", "Compile"); return { // Pre-link function pre: function (scope, element, attrs) { logLine("completeExampleLog", "nghDir2", "Pre-link"); }, // Post-link function post: function (scope, element, attrs) { logLine("completeExampleLog", "nghDir2", "Post-link"); } }; } }; }) .directive("nghDir3", function () { // Initialization logLine("completeExampleLog", "nghDir3", "Initialization"); // Definition object return { // Compile function compile: function (element, attrs) { logLine("completeExampleLog", "nghDir3", "Compile"); return { // Pre-link function pre: function (scope, element, attrs) { logLine("completeExampleLog", "nghDir3", "Pre-link"); }, // Post-link function post: function (scope, element, attrs) { logLine("completeExampleLog", "nghDir3", "Post-link"); } }; } }; }) .directive("nghDir4", function () { // Initialization logLine("completeExampleLog", "nghDir4", "Initialization"); // Definition object return { // Compile function compile: function (element, attrs) { logLine("completeExampleLog", "nghDir4", "Compile"); return { // Pre-link function pre: function (scope, element, attrs) { logLine("completeExampleLog", "nghDir4", "Pre-link"); }, // Post-link function post: function (scope, element, attrs) { logLine("completeExampleLog", "nghDir4", "Post-link"); } }; } }; }) .directive("nghDir5", function () { // Initialization logLine("completeExampleLog", "nghDir5", "Initialization"); // Definition object return { // Compile function compile: function (element, attrs) { logLine("completeExampleLog", "nghDir5", "Compile"); return { // Pre-link function pre: function (scope, element, attrs) { logLine("completeExampleLog", "nghDir5", "Pre-link"); }, // Post-link function post: function (scope, element, attrs) { logLine("completeExampleLog", "nghDir5", "Post-link"); } }; } }; }) .directive("nghDir6", function () { // Initialization logLine("completeExampleLog", "nghDir6", "Initialization"); // Definition object return { // Compile function compile: function (element, attrs) { logLine("completeExampleLog", "nghDir6", "Compile"); return { // Pre-link function pre: function (scope, element, attrs) { logLine("completeExampleLog", "nghDir6", "Pre-link"); }, // Post-link function post: function (scope, element, attrs) { logLine("completeExampleLog", "nghDir6", "Post-link"); } }; } }; });

Example


Description


In this example we're going to see how the directives are processed by AngularJS when it finds them in a HTML template and how we can write our own custom directives.

Initialization, compilation and linking phases

When AngularJS parses the HTML template to process directives, we can identify 3 main phases that each directive goes through:

  • initialization: this happens when a directive is found for the first time in the DOM tree traversal (so it happens just once even if the directive appears multiple times in the HTML template) and allows the directive to initialize itself internally if needed;
  • compilation: in this phase AngularJS manipulates the DOM of the HTML template and each directive has a chance to do some processing for each DOM node it appears in (so if the same directive appears in multiple DOM nodes, the compilation of the directive will take place for each node); in the compilation phase a directive also has a chance to modify the DOM node before a scope is attached to it;
  • linking: in this phase AngularJS attaches event listeners to the HTML template to make it interactive and attaches a scope to the directive and it does that for each DOM node the directive appears in; the linking phase takes place after the compilation of the whole HTML template has been executed; if the directive needs to access the scope, its link function allows that (see the details later in this description).

Defining a custom directive

We can define a custom directive through the directive method of an AngularJS module. The method accepts two parameters: the name of the new directive and a factory function.

Even though the name of a directive in the HTML template can be specified in different alternative ways (you can check the official AngularJS documentation to know more), let's keep it simple and learn the most common way to do it. Here are the common rules for a directive's name:

  • the name of the directive specified in the directive method must be camel case and must start with a lower-case letter
  • the name used in the HTML template is the dash-delimited version of the name and should be written all in lower-case (even though HTML is case-insensitive), so for each upper-case letter in the directive's name a dash symbol is inserted to have the HTML name

Here is an example: the directive's name myCustomDir becomes my-custom-dir in HTML. myCustomDir is called normalized directive name.

Compile and link functions in a custom directive definition

We talked about the initialization, compilation and linking phases so now it's time to see how a directive can execute its own actions in each one of those phases. Here is an example of a directive definition where we can decide to perform some processing in all the phases:

myModule.directive("myCustomDir", function ()
  {
    // Initialization
    // ...

    // Definition object
    return {
      // Compile function
      compile: function (element, attrs)
      {
        // ...

        return {
          // Pre-link function
          pre: function (scope, element, attrs)
          {
            // ...
          },
          // Post-link function
          post: function (scope, element, attrs)
          {
            // ...
          }
        };
      }
    };
  });

The first parameter of the directive method is the myCustomDir name of the directive and the second parameter is the factory function. Inside this function we can perform the initialization if we need and then we return the definition object. This object can have multiple properties that you can see detailed in the other examples on custom directives, but in this case we're only interested in the compile property. We assign a function to the compile property and this is our compile function. The function returns an object that allows us to specify the pre-link function and the post-link function by assigning them to the pre and post properties respectively. We talked earlier about the linking phase, so why are there two different link functions here? To understand better the meaning of this directive definition let's take a look at the following diagram:

Here we can see what happens when AngularJS parses the HTML template and processes the DOM nodes containing four custom directives dir1, dir2, dir3 and dir4 where the DOM tree is as follows:

<... dir1 ...>
  <... dir2 dir3 ...>
    <... dir4 dir1 ...>
    </...>
  </...>
</...>

The initialization and compilation phases are executed together in the first DOM tree traversal. Here are the processing steps:

  • the first node is processed and dir1 is found for the first time in the DOM tree, so its initialization part is executed and then its compile function is invoked for that DOM node;
  • after processing all the directives in the first DOM node, the parsing continues to the first child that in this case contains dir2 and dir3; these directives have never been found before in the DOM tree so all their initialization parts are executed and then all their compile functions are called;
  • the processing goes on to the next child node that contains dir4 and again dir1; dir1 was already previously found in the DOM tree so the initialization is not executed again, while the initialization of dir4 is executed because this directive has never been seen before in the DOM tree; the compile function of all the directives in the node is then executed because that's called for each DOM node.

If there are other nodes in the DOM tree of the HTML template after those we've seen, the processing simply goes on in the same way traversing the full tree from the top to the bottom and going in every single child node as soon as it's found in the tree traversal. After all this process, the initialization and compilation phases are finally completed and we have the final version of our DOM tree with all the nodes and their attributes.

The next phase is the linking phase. This is split into two parts: pre-linking and post-linking. Since AngularJS uses a depth-first strategy to explore the DOM tree, each time a DOM node is first visited all the pre-link functions of its directives are invoked, while during the backtracking phase of the search all the post-link functions are executed. In the pre-link function it is not safe to do DOM transformations (it's executed before the node's children are linked) while it's safe to do that in the post-link function.

IMPORTANT NOTE: you should not rely on the order of execution of the compile and link functions of the directives within a single DOM node because it's decided by AngularJS and could be different in future implementations; you can only rely on the order of execution based on the DOM tree traversal (parent-child relationships of the nodes and depth-first tree exploration strategy).

Different ways to declare the compile and link functions in custom directives

So far, we've seen a single way to declare a new directive in a module and that's the most complete because we have access to the compile, pre-link and post-link functions, but we don't necessary always need all of them and there are different ways to declare a directive depending on which functions we need. Let's see all of them.

  1. Without a definition object
    We can simply return the post-link function without specifying a definition object. This is shown in point 1 of the example.
    myModule.directive("myCustomDir", function ()
    {
      // Initialization
      // ...
    
      // Post-link function
      return function (scope, element, attrs)
      {
        // ...
      };
    });
        
  2. With a definition object and just the post-link function
    In the definition object we can associate the post-link function to the link property. This is shown in point 2 of the example.
    myModule.directive("myCustomDir", function ()
    {
      // Initialization
      // ...
    
      // Definition object
      return {
        // Post-link function
        link: function (scope, element, attrs)
        {
          // ...
        }
      };
    });
        
  3. With a definition object and the pre-link and post-link functions
    In the definition object we can associate an object to the link property and associate the pre-link function to the object's pre property and the post-link function to its post property. This is shown in point 3 of the example.
    myModule.directive("myCustomDir", function ()
    {
      // Initialization
      // ...
    
      // Definition object
      return {
        link: {
          // Pre-link function
          pre: function (scope, element, attrs)
          {
            // ...
          },
          // Post-link function
          post: function (scope, element, attrs)
          {
            // ...
          }
        }
      };
    });
        
  4. With a definition object and just the compile function
    In the definition object we can associate the compile function to the compile property. This is shown in point 4 of the example.
    myModule.directive("myCustomDir", function ()
    {
      // Initialization
      // ...
    
      // Definition object
      return {
        // Compile function
        compile: function (element, attrs)
        {
          // ...
        }
      };
    });
        
  5. With a definition object and the compile and post-link functions
    In the definition object we can associate the compile function to the compile property and return the post-link function directly from the compile function. If we specify the compile property of the definition object we cannot use its link property for the post-link function. This is shown in point 5 of the example.
    myModule.directive("myCustomDir", function ()
    {
      // Initialization
      // ...
    
      // Definition object
      return {
        // Compile function
        compile: function (element, attrs)
        {
          // ...
    
          // Post-link function
          return function (scope, element, attrs)
          {
            // ...
          };
        }
      };
    });
        
  6. With a definition object and the compile, pre-link and post-link functions
    In the definition object we can associate the compile function to the compile property and return an object where we associate the pre-link function to its pre property and the post-link function to its post property. This is shown in point 6 of the example. Points 7, 8 and 9 of the example use this kind of directive definition to show the different execution phases depending on the DOM tree structure.
    myModule.directive("myCustomDir", function ()
    {
      // Initialization
      // ...
    
      // Definition object
      return {
        // Compile function
        compile: function (element, attrs)
        {
          // ...
    
          return {
            // Pre-link function
            pre: function (scope, element, attrs)
            {
              // ...
            },
            // Post-link function
            post: function (scope, element, attrs)
            {
              // ...
            }
          };
        }
      };
    });
        

The parameters of the compile and link functions

We've see the compile and link functions defined inside our custom directives, but we didn't talk about their parameters. Just a quick note before we go on: AngularJS uses a lightweight version of jQuery called jqLite for its DOM search/manipulations, but if the HTML page loads also jQuery before the AngulaJS script file, then AngularJS will use jQuery instead of jqLite.

The signature of a compile function is as follows:

function (element, attrs)
  • element is a jqLite/jQuery object containing the DOM node being compiled (so if the directive is inside a div element, then inside element we'll have the div node wrapped in a jqLite/jQuery object)
  • attrs is an object and each attribute in the DOM node corresponds to a property in the attrs object (note that the name of the property is the normalized version of the attribute's name, for example, if my-attribute is specified in the DOM node, then the attrs object will have the myAttribute property and its value will be the actual value assigned to the attribute on the DOM)

NOTE: in some examples in books or on the web you could find that also the transclusion function is available as a parameter of the directive's compile function, but this has been deprecated and the link function should be used instead (see the official documentation for the deprecation notice and check the transclusion example for information about transclusion).

The signature of a link function is as follows:

function (scope, element, attrs)
  • scope is the scope associated to our directive by AngularJS
  • element is equivalent to the same parameter in the compile function
  • attrs is equivalent to the same parameter in the compile function

There could be a fourth parameter for the link function in case of required controllers. See the controller example for more information.

There could also be a fifth parameter for the link function if we need to access the transclusion function. See the transclusion example for more information.