I had went through the angular guide regarding the custom directives. I came across the link function in custom directive. I was unable to understand the concept & using the link function. Please can any one share a small functionality with explanation regarding the link function & it's parameters.
Directives are compiled inthe following steps
The $compile traverses the DOM and looks for directives. For each directive it finds, it adds it to a list of directives.
Once the entire DOM has been traversed, it will sort that list of directives by their priority. Then, each directive’s own compile function is executed, giving each directive the chance to modify the DOM itself. Each compile function returns a linking function, which is then composed into a combined linking function and returned.
$compile links the template with the scope by calling the combined linking function from the previous step. This in turn will call the linking function of the individual directives, registering listeners
Compile – This compiles an HTML string or DOM into a template and produces a template function, which can then be used to link scope and the template together.
Use the compile function to change the original DOM (template element) before AngularJS creates an instance of it and before a scope is created.
Post-Link – This is executed after the child elements are linked. It is safe to do DOM transformation in the post-linking function.
Use the post-link function to execute logic, knowing that all child elements have been compiled and all pre-link and post-link functions of child elements have been executed.
Pre-Link – This is executed before the child elements are linked. Not safe to do DOM transformation since the compiler linking function will fail to locate the correct elements for linking.
Use the pre-link function to implement logic that runs when AngularJS has already compiled the child elements, but before any of the child element's post-link functions have been called. Compile vs Link
var app = angular.module('app', []); function createDirective(name) { return function () { return { restrict: 'E', compile: function (tElem, tAttrs) { console.log(name + ': compile'); return { pre: function (scope, iElem, attrs) { console.log(name + ': pre link'); },
32
post: function (scope, iElem, attrs) { console.log(name + ': post link'); } } } } } } app.directive('levelOne', createDirective('level-One')); app.directive('levelTwo', createDirective('level-Two')); app.directive('levelThree', createDirective('level-Three')); Hello {{name}}
link function params follows
link: function(scope, element, attr) {
}
scope is an Angular scope object.
element is the jqLite-wrapped element that this directive matches.
attrs is an object with the normalized attribute names and their corresponding values
Related
I have this code:
app.directive('foo', function($compile) {
return {
restrict: 'E',
scope: {},
template: '<span>{{bar}}</span>',
compile: function(element, attrs) {
element.attr('title', '{{bar}}');
return function(scope, element, attrs) {
scope.bar = 'hello';
$compile(element)(scope);
}
}
}
});
Plunkr:
http://plnkr.co/edit/nFTgvYqoiFAthmjoizWS?p=preview
If I remove the $compile bit in the link function then the title attribute remains with the expression text ({{bar}}) and not the value ('hello');
Anyone can explain why?
I thought (from what I read in the docs) that this is what the compile phase is for - manipulating the template and preparing it for the link with scope and data binding. Why do I need to manually call $compile again? Isn't the template already compiled?
Maybe the phase names should be changed from compile, preLink, and postLink to postCompile, preLink, and postLink. The postCompile phase is availble to manipulate DOM before linking to a scope, at this point the linking function has been created but no scopes have been created. DOM can be added that requires no compilation. If additional elements are added that include directives or require interpolation, those additional elements need to be compiled and linked in order for the directives and interpolation to work.
To manupulate the template before compile, furnish a function to the template property: template: function(tElement, tAttrs) {}. For more information, see AngularJS Comprehensive Directive API Reference -- Template.
can you share a reference to "DOM can be added that requires no compilation, etc." or explain how did you found out about this?
Some sources of information:
AngularJS Developers Guide -- HTML Compiler
AngularJS Developers Guide -- Creating a Directive that Manipulates the DOM
In $compile there is a Pre-linking-function and in the AngularJS API Documentation they are write that it's not safe to do DOM transformation in the Pre-linking-function. Does anyone knows why?
Reference to section of API Documentation
First, what is linking?
linking is the process that angular uses to bind the model to the DOM.
The link function is responsible for registering DOM listeners as well as updating the DOM. It is executed after the template has been cloned. This is where most of the directive logic will be put. ~AngularJS API Documentation
Below is a really great diagram to show how Angular works
So why you can't manipulate the DOM in pre-linking?
Because pre-linking occurs before the linking happens (again this is the part where angular compiles and binds the model to the DOM so it can manipulate it). So during pre-linking angular is not aware of the DOM and its elements to be able to manipulate it at all.
To Reference the Diagram:
pre-linking would be before all of the arrows/lines are drawn between the boxes
linking would be the phase during the arrows/lines being drawn
post-linking would be after all of the arrows/lines are drawn and the pieces are connected
The main practical difference between pre and post linking functions lies in the fact that pre-linking functions are executed from parent directives to children directives, and post-linking functions are executed in the opposite order.
This is the result of $compile service behaviour which is explained in the manual:
Pre-linking function
Executed before the child elements are linked. Not safe to do DOM
transformation since the compiler linking function will fail to locate
the correct elements for linking.
Post-linking function
Executed after the child elements are linked.
In general this behaviour affects any situation where pre-linking function expects something from one of its children but will not get it because pre/post-linking functions in children (which run after parent's pre-link) didn't take that into account and messed up each other's controllers or DOM somehow. But the manual implies the very specific situation.
In this plunker
app.directive('abba', function () {
return {
template: '<a><b></b></a>',
link: {
pre: function (scope, element, attrs, ctrl) {
console.log('hi from this world')
element.find('a').remove()
}
}
}
});
app.directive('a', function () {
return {
link: {
pre: function (scope, element, attrs, ctrl) {
console.log('hi from that world')
}
}
};
});
app.directive('b', function () {
return {
link: {
pre: function (scope, element, attrs, ctrl) {
console.log('no hi from that world')
}
}
};
});
this will result in
hi from this world
hi from that world
Error: linkNode is undefined
nodeLinkFn#https://code.angularjs.org/1.4.8/angular.js:8330:9
Obviously, a directive is a living dead here, its pre-link is still alive. $compile also expects to link its children but failed to find the parent.
This won't happen if post-links are used instead:
yes hi from that world
hi from that world
hi from this world
so a and b can die peacefully after they served their purposes.
In a view there are multiple directives of the same type. How can one execute a function, once the last directive has been compiled and linked?
Details:
There are multiple instances of the same directive. Say, for example special image tags which are loaded lazily. Now each directive instance is compiled and linked independently - the scope of them is the parent controller. Once all the directive instances have been compiled and linked, they need to be activated. (One could activate them independently during the linking phase, but I would like to activate them all together instead). I need this functionality in order to port a jquery plugin to angular.
As each directive is compiled, keep a running count of the directives and add it to a list. The list is a variable stored within the directive itself. When the list of registered directives matches the count of compiled directives, you know when the last directive has been linked, and you can execute your custom function.
See Plunker Demo Here
app.directive('myDirective', function() {
var count = 0;
var elements = [];
return {
restrict: 'A',
compile: function(element, attr) {
++count;
return function(scope, element, attr) {
elements.push(element);
element.html('linking directive ' + elements.length);
if (count == elements.length)
{
alert('linked all ' + elements.length + ' directives');
}
}
}
}
});
I am trying to retrieve my template for a directive using a repository I have built that returns a promise that resolves to the contents of the template.
What is the difference between using the compile function in a directive and using the $compile service in the link function?
Compile Function
compile: function (element, attrs) {
templateRepository.get('Shared/Login').then(function (result) {
element.replaceWith(result);
});
}
This renders the HTML, but the scope is not bound to the DOM elements.
Using $compile
link: function (scope, elem, attrs) {
templateRepository.get('Shared/Login').then(function (result) {
elem.html(result);
$compile(elem.contents())(scope);
});
}
This works as expected.
What is the difference here?
$compile:
Compiles an HTML string or DOM into a template and produces a template
function, which can then be used to link scope and the template
together.
The compilation is a process of walking the DOM tree and matching DOM
elements to directives.
So $compile does the Angular processing on whatever DOM elements are handed to it.
During $compile the Compile Function within all found directives is run. Note that each directive's compile function is executed just once, no matter how many instances of that directive there are.
When the template function produced by $compile is executed ("to link scope and the template together") then each directive's link function is executed (with the scope passed in as the first parameter).
So $compile transforms the DOM. While the directive's compile function is what's run, for that directive, during that transformation.
Here's a little fiddle you can experiment with which shows the order of execution.
I think, Compile Function is a bit more agile:
From docs:
A compile function can have a return value which can be either a
function or an object.
returning a (post-link) function - is equivalent to registering the
linking function via the link property of the config object when the
compile function is empty.
returning an object with function(s) registered via pre and post
properties - allows you to control when a linking function should be
called during the linking phase. See info about pre-linking and
post-linking functions below.
I want to learn more about compile function and transclusion and found this Plnkr. This code contains a advanced tab directive. Based on this directive I started to create my own to see what happens and to see the values of the variables.
For my test I was playing with my 'button-bar'. When finished I had created this code:
testapp.directive('buttonBar', function($compile) {
return {
restrict: 'EA',
compile: function(cElement){
var title,
template;
template = "<div class='well'><div class='button-bar-title'></div></div>";
title = cElement.find(".title");
console.log("title:", title.html());
return function(scope, element, attributes){
var well = angular.element(template),
titleDiv = well.find(".button-bar-title");
titleDiv.append(title.html());
element.empty();
element.append(well);
$compile(titleDiv)(scope);
}
}
};
});
Nothing fancy. But when I looked at the code, I thought, why use a compile function? I think I can refactor this using a link function, so I did:
testapp.directive('buttonBar', function($compile) {
return {
restrict: 'EA',
link: function(scope, element, attributes){
var title,
template,
well,
titleDiv;
template = "<div class='well'><div class='button-bar-title'></div></div>";
title = element.find(".title");
console.log("title:", title.html());
well = angular.element(template),
titleDiv = well.find(".button-bar-title");
titleDiv.append(title.html());
element.empty();
element.append(well);
$compile(titleDiv)(scope);
}
};
});
And this gave me the exact same result.
At this point I am confused, why should the originally author of the plunker use a compile function instead of a link function? Is there a good reason for? Or are there guidelines on when to use a compile or a link function?
Edit:
For both directives I've used the same HTML (primary1Label is defined on the controller):
<button-bar>
<div class="title">De titel is {{primary1Label}}</div>
</button-bar>
In the example you provided compile is the correct method, as it executes before the template is cloned and therefore performs less manipulation on the DOM.
The most fundamental difference between the two is that compile runs once for all directives on the DOM, whereas link runs for each individual element.
As a result it's recommended that if you want to perform a template DOM manipulation you should do so within the compile function.
If you want to listen to scope variables individually and perform instance DOM manipulation (i.e. on this element only) then the link function is what you want.
There's another answer here What is the difference between compile and link function in angularjs that explains it more thoroughly.