Transclusion is no doubt a very confusing topic in AngularJS. It has two ways of implmentation :
1) transclude: 'true'
2) transclude: 'element'
as far as transclude: 'true' goes, one can grasp it with little effort but transclude: 'element' seems like a lot of work .
So, what thus transclude:'element' do ?
It transcludes the whole element ,i.e, it copies the content inside the directive as well as the directive tag itself and places one or more copies of it with the correct scope .
The transclusion is achieved via transclusion function unlike the more simpler ng-transclude in case of transclude: 'true' .
For a quick review of transclude:'true' , to achieve transclusion , we included <ng-transclude> wherever we wanted our element to appear inside the 'template' property like :
but when we do transclude: 'element', we cannot use a template , so we can't ng-transclude it , so instead, as i just mentioned , we will use transclusion function . Transclusion function is simply a link function , which is returned after compiling the transcluded elements .
To give you an overview of how transclusion is achieved by the browser, the flow is as follows :
So, basically , we are getting the contents of the element containing the directive which has transclusion enabled in the first line and storing them in a variable . Then we are removing the contents of the element from the DOM . In the third line , we are compiling the variable which has the contents of the element and producing the transclusion function (transcludeFn) .
This function is given as the fifth parameter of 'link' function . It was earlier used in 'compile' function as well but has become obsolete now .
This Transclude function can accept a scope and a call back function , and this function returns a clone of the transcluded element instead of the original element like :
This callback function is synchronous not asynchronous .
Now, its important to understand that this transcludeFn places the transcluded element and the location where you want to place can be achieved by Jquery style DOM traversal and modification . Like if i want to put the transcluded element after the 'element' , which is the second parameter of the link function , i can simply do :
Or i can attach a clone of the transcluded element to the DOM :
In the above two examples of transclusion, the transcluded elements get an inherited scope from the parent scope . Now there is an element of confusion here . The parent scope i am referring to is the original scope not the scope of the directive which has transclusion enabled.
Let me give an example :
suppose i have done transclude :element for my custom directive
note that i have isolated the scope of the directive , so in normal sense , anything inside this directive should not inherit from the parent controller scope
app.js :
and in my html
index.html:
{{$id}} is a very neat way to get the scope id .
{{$parent.$id}} gives the scope id of the parent scope .
i have used {{Var }} in the transcluded element , which is defined only in the parent controller .
So , now in my browser, i can see :
Did you see that , did you see that ? That guy is inheriting . 'Var' is being fetched from the parent controller 'mainController' , even though the directive has isolate scope . This behaviour of transclude element is desirable to avoid conflicts between scopes,scope leaking .
Now getting back to the features of transcludeFn , though the default scope of transcluded elements is like the one i mentioned above, you can also provide your own custom scope to it like :
so in the above case, i gave the first parameter as scope, which is also the first parameter of the link function , so you guessed it right , we are assigning it the scope of the custom directive . And since the directive has isolate scope, it will not read any properties from the parent scope and that {{Var}} should not evaluate , since it is not visible now to the transcluded element scope . Right? Yes ofcourse .
Our view is now :
Dont get confused by that line 'and i am inherited from : 2', since i used $parent in that and you can access the immediate parent in isolate scopes through $parent , which may sound confusing to some .
Now, lets talk something about the compiling and linking of transcludeFn .
when you do something like :
and when you do something like :
So, what is its importance . One is that, if you want to clone the transcluded element several times , like in the case of ng-repeat , you hve to insert the clones into html before linking . They will have individual scopes of their own , again like in ng-repeat ..
Lets further extend our app.js above
Here you can see we are doing five iterations of the for loop and embedding a clone in our html for each iteration . every clone has its own scope id.
Our view now :
This happens because we are inserting the html before linking, we wont be able to add clones, if we have already linked the element, like , if we do
we will only get a single element in our html :
Our view now :
There are many complex implementations of transclde: 'element' , but as an introduction and understanding to transclude: 'element' , this can get you going .
Cheers .
1) transclude: 'true'
2) transclude: 'element'
as far as transclude: 'true' goes, one can grasp it with little effort but transclude: 'element' seems like a lot of work .
So, what thus transclude:'element' do ?
It transcludes the whole element ,i.e, it copies the content inside the directive as well as the directive tag itself and places one or more copies of it with the correct scope .
The transclusion is achieved via transclusion function unlike the more simpler ng-transclude in case of transclude: 'true' .
For a quick review of transclude:'true' , to achieve transclusion , we included <ng-transclude> wherever we wanted our element to appear inside the 'template' property like :
transclude: 'true',template: '<some tags here><div ng-transclude><some tags here>',
but when we do transclude: 'element', we cannot use a template , so we can't ng-transclude it , so instead, as i just mentioned , we will use transclusion function . Transclusion function is simply a link function , which is returned after compiling the transcluded elements .
To give you an overview of how transclusion is achieved by the browser, the flow is as follows :
var elementsToTransclude = directiveElement.contents();
directiveElement.html(' ');
var transcludeFn= $compile(elementsToTransclude);
So, basically , we are getting the contents of the element containing the directive which has transclusion enabled in the first line and storing them in a variable . Then we are removing the contents of the element from the DOM . In the third line , we are compiling the variable which has the contents of the element and producing the transclusion function (transcludeFn) .
This function is given as the fifth parameter of 'link' function . It was earlier used in 'compile' function as well but has become obsolete now .
link : function(scope, element, attributes, controller, transcludeFn){}
This Transclude function can accept a scope and a call back function , and this function returns a clone of the transcluded element instead of the original element like :
transcludeFn(scope,function(clone){});
This callback function is synchronous not asynchronous .
Now, its important to understand that this transcludeFn places the transcluded element and the location where you want to place can be achieved by Jquery style DOM traversal and modification . Like if i want to put the transcluded element after the 'element' , which is the second parameter of the link function , i can simply do :
element.after(transcludeFn());
Or i can attach a clone of the transcluded element to the DOM :
transcludeFn(function(clone){
element.after(clone);
})
In the above two examples of transclusion, the transcluded elements get an inherited scope from the parent scope . Now there is an element of confusion here . The parent scope i am referring to is the original scope not the scope of the directive which has transclusion enabled.
Let me give an example :
suppose i have done transclude :element for my custom directive
note that i have isolated the scope of the directive , so in normal sense , anything inside this directive should not inherit from the parent controller scope
app.js :
var app=angular.module('mainModule',[]);
app.controller('mainController',function($scope){
$scope.Var='Hello I am you father';
});
app.directive('customDirective',function(){
return {
restrict: 'A',
scope: {},
transclude: 'element',
link: function(scope,element,attrs,controller,transcludeFn){
transcludeFn(function(clone){
element.after(clone);
});
}
}
});
and in my html
index.html:
<body ng-app="mainModule">
<div ng-controller="mainController">
Fathers scope is : {{$id}}
<div custom-directive>Hello People Socpe id is : {{$id}} and i am inherited from: {{$parent.$id}} {{Var}}</div>
</div>
</body>
{{$id}} is a very neat way to get the scope id .
{{$parent.$id}} gives the scope id of the parent scope .
i have used {{Var }} in the transcluded element , which is defined only in the parent controller .
So , now in my browser, i can see :
Fathers scope is : 2
Hello People scope id is: 4 and i am inherited from : 2 Hello i am your father .
Did you see that , did you see that ? That guy is inheriting . 'Var' is being fetched from the parent controller 'mainController' , even though the directive has isolate scope . This behaviour of transclude element is desirable to avoid conflicts between scopes,scope leaking .
Now getting back to the features of transcludeFn , though the default scope of transcluded elements is like the one i mentioned above, you can also provide your own custom scope to it like :
transcludeFn(scope,function(clone){});
so in the above case, i gave the first parameter as scope, which is also the first parameter of the link function , so you guessed it right , we are assigning it the scope of the custom directive . And since the directive has isolate scope, it will not read any properties from the parent scope and that {{Var}} should not evaluate , since it is not visible now to the transcluded element scope . Right? Yes ofcourse .
Our view is now :
Fathers scope is : 2
Hello People scope id is: 3 and i am inherited from : 2
Dont get confused by that line 'and i am inherited from : 2', since i used $parent in that and you can access the immediate parent in isolate scopes through $parent , which may sound confusing to some .
Now, lets talk something about the compiling and linking of transcludeFn .
when you do something like :
transcludeFn(scope, function(clone){element.after(clone) // this is compiled but not linked yet})
and when you do something like :
var transcluded=transcludeFn();element.after(transluded) // this is compiled and linked .
So, what is its importance . One is that, if you want to clone the transcluded element several times , like in the case of ng-repeat , you hve to insert the clones into html before linking . They will have individual scopes of their own , again like in ng-repeat ..
Lets further extend our app.js above
var app=angular.module('mainModule',[]);
app.controller('mainController',function($scope){
$scope.Var='Hello I am you father';
});
app.directive('customDirective',function(){
return {
restrict:'A',
scope:{},
transclude: 'element',
link: function(scope,element,attrs,controller,transcludeFn){
for (var i = 5; i >= 0; i--) {
trscluded=transcludeFn(function(clone){
element.after(clone);
});
}
}
}
});
Here you can see we are doing five iterations of the for loop and embedding a clone in our html for each iteration . every clone has its own scope id.
Our view now :
Fathers scope is :2Hello People Socpe id is : 8 and i am inherited from: 2 Hello I am you fatherHello People Socpe id is : 7 and i am inherited from: 2 Hello I am you fatherHello People Socpe id is : 6 and i am inherited from: 2 Hello I am you fatherHello People Socpe id is : 5 and i am inherited from: 2 Hello I am you fatherHello People Socpe id is : 4 and i am inherited from: 2 Hello I am you father
This happens because we are inserting the html before linking, we wont be able to add clones, if we have already linked the element, like , if we do
var app=angular.module('mainModule',[]);app.controller('mainController',function($scope){$scope.Var='Hello I am you father';});app.directive('customDirective',function(){return {restrict:'A',scope:{},transclude: 'element',link: function(scope,element,attrs,controller,transcludeFn){for (var i = 5; i >= 0; i--) {element.after(transcludeFn());}}}});
we will only get a single element in our html :
Our view now :
Fathers scope is :2Hello People Socpe id is : 8 and i am inherited from: 2 Hello I am you father
There are many complex implementations of transclde: 'element' , but as an introduction and understanding to transclude: 'element' , this can get you going .
Cheers .