Exploring the Angular Directive Life Cycle

I recently dropped into a fairly advanced Angular project and needed to get up to speed with custom directive creation quickly. This blog post by K. Scott Allen really helped with the fundamentals of directive events but I was thrown for a loop by templates loaded inside ng-include directives. After testing, it appears that the asynchronous loading of ng-include contents means that the ng-include’s contents will be processed after the containing directive’s post link method is called.

This makes complete sense after further reflection, but can be confusing at first. The following code (and Plunkr Demo) illustrates the angular directive life cycle through several layers of ng-includes:

index.html

<!DOCTYPE html>
<html ng-app="exploration">

<head>
  <script data-require="angular.js@*" data-semver="1.2.18" src="https://code.angularjs.org/1.2.18/angular.js"></script>
  <script src="script.js"></script>
</head>

<body ng-controller="mainController as main">
  <div outermost>
    <h2>Test Items</h2>
    <ul>
      <li ng-repeat="item in main.repeatItems" on-repeat>
        <span ng-include="'repeatItemTemplate.html'"></span>
      </li>
    </ul>
  </div>

  <h2>Log Output</h2>
  <ul style="list-style:none; padding-left:0">
    <li ng-style="{ 'padding-left': '0px'}"
        ng-repeat="message in main.messages">
        <code> - </code>
    </li>
  </ul>
</body>

</html> 

repeatItemTemplate.html

<span in-template>
  <span ng-include="'repeatItemSubTemplate.html'"></span>
</span>

repeatItemSubTemplate.html

<span in-sub-template></span>

script.js

(function() {
    var module = angular.module("exploration",[]);

    var messages = [];
    var logMessage = function(message, indentationLevel) {
        messages.push({ message: message, timestamp: new Date(), indentationLevel: indentationLevel });
        console.log(message);
    };

    module.controller("mainController", ["$scope", function($scope){
        logMessage("mainController");

        var self = this;

        self.messages = messages;
        self.repeatItems = [ "one", "two", "three" ];

        var requestedCount = 0;
        var stopRequestListener = $scope.$on('$includeContentRequested', function () {
            requestedCount++;
        });

        var loadedCount = 0;
        var stopLoadListener = $scope.$on('$includeContentLoaded', function () {
            loadedCount++;
            logIncludeLoadEventCounts();
        });

        var errorCount = 0;
        var stopErrorListener = $scope.$on('$includeContentError', function () {
            errorCount++;
            logIncludeLoadEventCounts();
        });

        var logIncludeLoadEventCounts = function(){
            logMessage("mainController - include load events - requestedCount: " + requestedCount + " loadedCount: " + loadedCount);
        };
    }]);

    var createDirective = function(directiveName, indentationLevel) {
        module.directive(directiveName, ["$timeout", function($timeout){
            return {
                link: {
                    pre: function (scope, element, attrs) {
                        logMessage(directiveName + " - prelink", indentationLevel);
                    },
                    post: function (scope, element, attrs) {
                        logMessage(directiveName + " - postlink", indentationLevel);

                        scope.$evalAsync(function() {
                            logMessage(directiveName + " - postlink - scope.$evalAsync", indentationLevel);
                        }, 0);

                        $timeout(function() {
                            logMessage(directiveName + " - postlink - timeout", indentationLevel);
                        }, 0);
                    },
                },
                controller: function() {
                    logMessage(directiveName + " - controller", indentationLevel);
                },
            };
        }]);
    };

    createDirective("outermost", 1);
    createDirective("onRepeat", 2);
    createDirective("inTemplate", 3);
    createDirective("inSubTemplate", 4);
})();

Output

Test Items

  • one
  • two
  • three

Log Output

  • 14:47:05.586 - mainController
  • 14:47:05.586 - outermost - controller
  • 14:47:05.586 - outermost - prelink
  • 14:47:05.588 - outermost - postlink
  • 14:47:05.589 - outermost - postlink - scope.$evalAsync
  • 14:47:05.590 - onRepeat - controller
  • 14:47:05.590 - onRepeat - prelink
  • 14:47:05.591 - onRepeat - postlink
  • 14:47:05.592 - onRepeat - controller
  • 14:47:05.592 - onRepeat - prelink
  • 14:47:05.592 - onRepeat - postlink
  • 14:47:05.592 - onRepeat - controller
  • 14:47:05.592 - onRepeat - prelink
  • 14:47:05.592 - onRepeat - postlink
  • 14:47:05.605 - onRepeat - postlink - scope.$evalAsync
  • 14:47:05.605 - onRepeat - postlink - scope.$evalAsync
  • 14:47:05.605 - onRepeat - postlink - scope.$evalAsync
  • 14:47:05.637 - outermost - postlink - timeout
  • 14:47:05.639 - onRepeat - postlink - timeout
  • 14:47:05.642 - onRepeat - postlink - timeout
  • 14:47:05.645 - onRepeat - postlink - timeout
  • 14:47:05.662 - inTemplate - controller
  • 14:47:05.662 - inTemplate - prelink
  • 14:47:05.663 - inTemplate - postlink
  • 14:47:05.663 - mainController - include load events - requestedCount: 3 loadedCount: 1
  • 14:47:05.664 - inTemplate - controller
  • 14:47:05.664 - inTemplate - prelink
  • 14:47:05.664 - inTemplate - postlink
  • 14:47:05.664 - mainController - include load events - requestedCount: 3 loadedCount: 2
  • 14:47:05.665 - inTemplate - controller
  • 14:47:05.665 - inTemplate - prelink
  • 14:47:05.665 - inTemplate - postlink
  • 14:47:05.665 - mainController - include load events - requestedCount: 3 loadedCount: 3
  • 14:47:05.666 - inTemplate - postlink - scope.$evalAsync
  • 14:47:05.666 - inTemplate - postlink - scope.$evalAsync
  • 14:47:05.666 - inTemplate - postlink - scope.$evalAsync
  • 14:47:05.687 - inTemplate - postlink - timeout
  • 14:47:05.691 - inTemplate - postlink - timeout
  • 14:47:05.694 - inTemplate - postlink - timeout
  • 14:47:05.720 - inSubTemplate - controller
  • 14:47:05.720 - inSubTemplate - prelink
  • 14:47:05.720 - inSubTemplate - postlink
  • 14:47:05.720 - mainController - include load events - requestedCount: 6 loadedCount: 4
  • 14:47:05.721 - inSubTemplate - controller
  • 14:47:05.721 - inSubTemplate - prelink
  • 14:47:05.721 - inSubTemplate - postlink
  • 14:47:05.721 - mainController - include load events - requestedCount: 6 loadedCount: 5
  • 14:47:05.721 - inSubTemplate - controller
  • 14:47:05.722 - inSubTemplate - prelink
  • 14:47:05.722 - inSubTemplate - postlink
  • 14:47:05.722 - mainController - include load events - requestedCount: 6 loadedCount: 6
  • 14:47:05.722 - inSubTemplate - postlink - scope.$evalAsync
  • 14:47:05.722 - inSubTemplate - postlink - scope.$evalAsync
  • 14:47:05.722 - inSubTemplate - postlink - scope.$evalAsync
  • 14:47:05.745 - inSubTemplate - postlink - timeout
  • 14:47:05.749 - inSubTemplate - postlink - timeout
  • 14:47:05.756 - inSubTemplate - postlink - timeout


comments powered by Disqus