EDIT: Completely and totally changing my answer and marking this as "Community Wiki" (meaning no points for me) as I was outright wrong when I answered this
As @Jonah pointed out below, here is a really good article on the compile option of directives and using the transclusion function
The basic idea is the compile function should return a linking function. You can use the transclusion function provided inside the linking function to take a clone of the transcluded DOM element, compile it, and insert it wherever it needs to be inserted.
Here is a better example I've pulled out of my butt on Plunker
The idea of the compile function is it gives you a chance to programmatically alter the DOM elements based on attributes passed BEFORE the linking function is created and called.
// a silly directive to repeat the items of a dictionary object.
app.directive('keyValueRepeat', function ($compile){
return {
transclude: true,
scope: {
data: '=',
showDebug: '@'
},
compile: function(elem, attrs, transclude) {
if(attrs.showDebug) {
elem.append('<div class="debug">DEBUG ENABLED {{showDebug}}</div>');
}
return function(scope, lElem, lAttrs) {
var items = [];
console.log(lElem);
scope.$watch('data', function(data) {
// remove old values from the tracking array
// (see below)
for(var i = items.length; i-- > 0;) {
items[i].element.remove();
items[i].scope.$destroy();
items.splice(i,1);
}
//add new ones
for(var key in data) {
var val = data[key],
childScope = scope.$new(),
childElement = angular.element('<div></div>');
// for each item in our repeater, we're going to create it's
// own scope and set the key and value properties on it.
childScope.key = key;
childScope.value = val;
// do the transclusion.
transclude(childScope, function(clone, innerScope) {
//clone is a copy of the transcluded DOM element content.
console.log(clone);
// Because we're still inside the compile function of the directive,
// we can alter the contents of each output item
// based on an attribute passed.
if(attrs.showDebug) {
clone.prepend('<span class="debug">{{key}}: {{value}}</span>');
}
//append the transcluded element.
childElement.append($compile(clone)(innerScope));
});
// add the objects made to a tracking array.
// so we can remove them later when we need to update.
items.push({
element: childElement,
scope: childScope
});
lElem.append(childElement);
}
});
};
}
};
});
Why do I have to use "{{title}}" with '@' and "title" with '='?
@ binds a local/directive scope property to the evaluated value of the DOM attribute. If you use title=title1
or title="title1"
, the value of DOM attribute "title" is simply the string title1
. If you use title="{{title}}"
, the value of the DOM attribute "title" is the interpolated value of {{title}}
, hence the string will be whatever parent scope property "title" is currently set to. Since attribute values are always strings, you will always end up with a string value for this property in the directive's scope when using @.
= binds a local/directive scope property to a parent scope property. So with =, you use the parent model/scope property name as the value of the DOM attribute. You can't use {{}}
s with =.
With @, you can do things like title="{{title}} and then some"
-- {{title}} is interpolated, then the string "and them some" is concatenated with it. The final concatenated string is what the local/directive scope property gets. (You can't do this with =, only @.)
With @, you will need to use attr.$observe('title', function(value) { ... })
if you need to use the value in your link(ing) function. E.g., if(scope.title == "...")
won't work like you expect. Note that this means you can only access this attribute asynchronously.
You don't need to use $observe() if you are only using the value in a template. E.g., template: '<div>{{title}}</div>'
.
With =, you don't need to use $observe.
Can I also access the parent scope directly, without decorating my element with an attribute?
Yes, but only if you don't use an isolate scope. Remove this line from your directive
scope: { ... }
and then your directive will not create a new scope. It will use the parent scope. You can then access all of the parent scope properties directly.
The documentation says "Often it's desirable to pass data from the isolated scope via an expression and to the parent scope", but that seems to work fine with bidirectional binding too. Why would the expression route be better?
Yes, bidirectional binding allows the local/directive scope and the parent scope to share data. "Expression binding" allows the directive to call an expression (or function) defined by a DOM attribute -- and you can also pass data as arguments to the expression or function. So, if you don't need to share data with the parent -- you just want to call a function defined in the parent scope -- you can use the & syntax.
See also
Best Answer
Consider a directive called myDirective in an element, and that element is enclosing some other content, let's say:
If myDirective is using a template, you'll see that the content of
<div my-directive>
will be replaced by your directive template. So having:will result in this render:
Notice that the content of your original element
<div my-directive>
will be lost (or better said, replaced). So, say good-bye to these buddies:So, what if you want to keep your
<button>...
and<a href>...
in the DOM? You'll need something called transclusion. The concept is pretty simple: Include the content from one place into another. So now your directive will look something like this:This would render:
In conclusion, you basically use transclude when you want to preserve the contents of an element when you're using a directive.
My code example is here. You could also benefit from watching this.