Angular directives have a lifecycle that involves several phases: compilation, linking (pre-link and post-link), and controller execution. Understanding these phases is crucial for efficiently managing directive behavior and DOM manipulation.
Directive Definition Object and Linking Functions
The directive definition object in Angular can have both compile and link properties. Their interaction is as follows:
- If a
compilefunction is present, thelinkfunction is ignored. The return value of thecompilefunction is used as the linking function. - If
compileis absent butlinkis present, Angular wraps the user-definedlinkfunction usingdirective.compile = valueFn(directive.link);. - If both
compileandlinkare present, thelinkproperty is ignored, and thecompilefunction's return value dictates the linking behavior.
The link function itself can be structured in two ways:
- If it's a single function, it's treated as a
postLinkfunction. - If it's an object with
preandpostproperties, these are treated as thepreLinkandpostLinkfunctions, respective.
app.directive('myDirective', function() {
return {
compile: function() {
console.log('Compile phase');
// Returning an object to define pre and post link functions
return {
pre: function(scope, element, attrs) {
console.log('Pre-link phase');
},
post: function(scope, element, attrs) {
console.log('Post-link phase');
}
};
}
};
});
Execution Order
Angular processes directives starting from the root element annotated with ng-app, recursively traversing down the DOM. Each directive undergoes compilation and linking.
- Compilation: The
compilefunction is executed once per directive definition, regardless of how many instances of the directive exist. - Linking: The
linkfunction (includingpreLinkandpostLink) is executed for each directive instance.
Compile Phase Order
Compile functions are executed in a depth-first, left-to-right manner: from parent to child elements, and then across sibling elements, respecting directive priorities.
Pre-link and Post-link Phase Order
After all compile functions have run, the linking phase begins.
- Pre-link: Executed in a depth-first, top-to-bottom order (parent to child). For a given element, its
preLinkruns before its children'spreLinkandpostLink. - Post-link: Executed in a bottom-up, right-to-left order (child to parent). For a given element, its
postLinkruns after all its children'spreLinkandpostLinkhave completed.
The overall linking process follows this flow:
- Execute the current element's directive's
preLink. - If the element has child directives, recursively execute their
preLinkfunctions. - Once all child
preLinkfunctions are done, execute the current element's directive'spostLink. - If the element has sibling directives, move to the next sibling and repeat the process from its
preLink. - After an element's
postLinkcompletes and it has no more siblings to process, its parent'spostLinkcan execute.
Purpose of Each Phase
Compile Function
The compile function receives the template element and its attributes. It's executed before any scope or element instances are created. This phase is ideal for:
- Modifying the template DOM structure.
- Performing operations that should occur only once per directive definition, like cloning template structures for use in directives like
ng-repeat. This can improve performance by avoiding repeated DOM manipulation for multiple instances.
Note: The scope is not available during the compile phase.
Pre-link Function
The preLink function runs after compile and before the directive's child elements are linked. It receives the scope and the element instance.
- Ideal for passing data or setting up scope properties that child directives might need during their own linking phase.
Post-link Function
The postLink function executes after the directive's children have completed their linking phases. It receives the scope and the element instance.
- This is the most common place for DOM manipulation, event binding, and interacting with child elements becuase the entire DOM subtree for the directive is stabilized.
- Considered the safest place for most business logic related to the directive's instance.
Controller
The controller is instantiated after the compile phase and *before* the preLink phase.
- Controllers can define data or methods that are accessible to the directive's linking functions (
preLinkandpostLink). - They can also expose an API that other directives on the same element can interact with. Directives can require another directive's controller using the
requireproperty in their definition.
A controller can be required by another directive by referencing its name, often in the format 'directiveNameController'.
// Example directive with a controller and linking functions
app.directive('parentDirective', function() {
return {
restrict: 'E',
controller: function($scope, $element, $attrs) {
this.sharedData = 'Data from parent controller';
console.log('Parent: Controller');
},
compile: function(tElement, tAttrs) {
console.log('Parent: Compile');
return {
pre: function(scope, iElement, iAttrs) {
console.log('Parent: Pre-link');
// Can potentially access controller if required, but not ideal here
},
post: function(scope, iElement, iAttrs) {
console.log('Parent: Post-link');
// Access controller via iElement.controller('parentDirective') or scope.$parent.parentDirective if exposed
}
};
}
};
});
app.directive('childDirective', function() {
return {
restrict: 'E',
require: '^^parentDirective', // Require parent directive's controller
link: function(scope, iElement, iAttrs, parentCtrl) {
console.log('Child: Link');
console.log('Child received from parent:', parentCtrl.sharedData);
},
compile: function(tElement, tAttrs) {
console.log('Child: Compile');
return {
pre: function(scope, iElement, iAttrs) {
console.log('Child: Pre-link');
},
post: function(scope, iElement, iAttrs) {
console.log('Child: Post-link');
}
}
}
};
});