What are the benefits of a directive template function in Angularjs? - javascript

According to the documentation a template can be a function which takes two parameters, an element and attributes and returns a string value representing the template. It replaces the current element with the contents of the HTML. The replacement process migrates all of the attributes and classes from the old element to the new one.
The compile function deals with transforming the template DOM. It takes three parameters, an element, attributes and transclude function. The transclude parameter has been deprecated. It returns a link function.
It appears that a template and a compile functions are very similar and can achieve the same goal. The template function defines a template and compile function modifies the template DOM. However, it can be done in the template function itself. I can't see why modify the template DOM outside the template function. And vice-versa if the DOM can be modified in the compile function then what's the need for a template function?

The compilation function can be used to change the DOM before the resulting template function is bound to the scope.
Consider the following example:
<div my-directive></div>
You can use the compile function to change the template DOM like this:
app.directive('myDirective', function(){
return {
// Compile function acts on template DOM
// This happens before it is bound to the scope, so that is why no scope
// is injected
compile: function(tElem, tAttrs){
// This will change the markup before it is passed to the link function
// and the "another-directive" directive will also be processed by Angular
tElem.append('<div another-directive></div>');
// Link function acts on instance, not on template and is passed the scope
// to generate a dynamic view
return function(scope, iElem, iAttrs){
// When trying to add the same markup here, Angular will no longer
// process the "another-directive" directive since the compilation is
// already done and we're merely linking with the scope here
iElem.append('<div another-directive></div>');
}
}
}
});
So you can use the compile function to change the template DOM to whatever you like if your directive requires it.
In most cases tElem and iElem will be the same DOM element, but sometimes it can be different if a directive clones the template to stamp out multiple copies (cf. ngRepeat).
Behind the scenes, Angular uses a 2-way rendering process (compile + link) to stamp out copies of a compiled piece of DOM, to prevent Angular from having to process (= parse directives) the same DOM over and over again for each instance in case the directive stamps out multiple clones resulting in much better performance.
Hope that helps!
ADDED AFTER COMMENT:
The difference between a template and compile function:
Template function
{
template: function(tElem, tAttrs){
// Generate string content that will be used by the template
// function to replace the innerHTML with or replace the
// complete markup with in case of 'replace:true'
return 'string to use as template';
}
}
Compile function
{
compile: function(tElem, tAttrs){
// Manipulate DOM of the element yourself
// and return linking function
return linkFn(){};
}
}
The template function is called before the compile function is called.
Although they can perform almost identical stuff and share the same 'signature', the key difference is that the return value of the template function will replace the content of the directive (or the complete directive markup if replace: true), while a compile function is expected to change the DOM programmatically and return a link function (or object with pre and post link function).
In that sense you can think of the template function as some kind of convenience function for not having to use the compile function if you simply need to replace the content with a string value.
Hope that helps!

One of the best uses of the template function is to conditionally generate a template. This allows you to automate the creation of a template based on an attribute or any other condition.
I have seen some very large templates that use ng-if to hide sections of the template. But instead of placing everything into the template and using ng-if, which can cause excessive binding, you can remove sections of the DOM from the output of the template function that will never be used.
Let's say you have a directive that will include either sub-directive item-first or item-second. And the sub-directive will not ever change for the life of the outer directive. You can adjust the output of the template, prior to the compile function being called.
<my-item data-type="first"></my-item>
<my-item data-type="second"></my-item>
And the template string for these would be:
<div>
<item-first></item-first>
</div>
and
<div>
<item-second></item-second>
</div>
I agree that this is an extreme simplification, But I have some very complicated directives and the outer directive needs to display one of, about, 20 different inner directives based on a type. Instead of using transclude, I can set the type on the outer directive and have the template function generate the correct template with the correct inner directive.
That correctly formatted template string is then passed on to the compile function, etc.

Related

AngularJS get the template or original expression

I'm wondering if there is any way I can get the original {{expression}} after angular has complied the directives and interpolated the expressions. For instance if there is a text e.g. Hi, Filip and the user clicks on it, I want to be able to show a pop-up with Hi, {{name}}.
Now, one way I thought of doing that is by analysing the DOM before angular (e.g. during run) and then saving the expressions as additional attributes to the parent element. However, I run into various problems with that (e.g. if parent has other child elements and they are removed, e.g. with ng-if, then I can't reliably know which expression belongs to which text node).
Since Angular keeps watchers for these expressions, it must have a reference to the text nodes they are applied on. Is there any way I could access those?
The second question is, can I somehow get the original element of ng-repeat (before it was compiled and transcluded), for the similar purpose (allowing the user to modify it on-the-fly).
I want to avoid introducing new directives as this is meant to work on existing angular applications.
I'm not concerned about performance or security (i.e. this is not for production applications but rather for prototyping/debugging).
Use factories to supply to your html expressions with reusable logic. Assumes you're using controller as syntax so you can the controller's scope as this in your view.
// factory
function() {
return 'bob';
}];
// in your controller
['somefactory', function(factory) {
this.factoryString = factory.toString(); // => "function() { return 'bob'; }"
this.factory = factory;
}];
// view
<div>hi {{this.factory()}} you were made with {{this.factoryString}}</div>
// interpolated
<div>hi bob, you were made with function() { return 'bob' }</div>
I didn't test any of that though.

In angularjs, when making a directive, is there a way to manipulate the DOM of the template using the link function?

Let's say I'm creating a dynamic directive in angularjs. I want to use the link function to manipulate the DOM of the template html based on the arguments.
So in vanilla javascript, I would do something like the following:
var template = ... //something here that sets the variable to the template
var newdiv = document.createElement("div");
template.appendChild(newdiv);
I found some answers where they treat the template like a string and just splice in the literal string "<div></div>".
However, I plan to do a lot of modification so treating it as a string will quickly get too confusing, and will be unmaintainable if I do it. If possible, I would like to treat it the same way I treat the page's DOM in regular js.
I am also open to having no template, and just dynamically generating the whole thing in the link function, if it's possible for me to somehow get the directive to return this
Compiled element can be modified in link function. No bindings or directives can be added to the element at this point without recompilation.
...
link: function (scope, element, attrs) {
// jqLite element that partly implements jQuery API
element.append(...);
// native element that is wrapped with jqLite
var nativeElement = element[0];
nativeElement.appendChild(...);
}

AngularJS: Dynamically add/remove wrapping directives to existing ones

So I need to be able to call a function of a directives controller, have it wrap an existing child directive in a new one while maintaining the now wrapped directive's state (both it's controller and all possible children) and otherwise modify the parent directive's template. This process also has to able to reversed.
Quick and super dirty fiddle to show the idea
So the DOM starts as:
<split>
<innerDirective></innerDirective>
</split>
When the split directive's controller's function is called, this should happen:
<split> // same outer parent
<split> // new directive
<innerDirective></innerDirective>// SAME innerDirective instance
</split>
<split>// new directive
<innerDirective></innerDirective> //New innderDirective instance
</split>
</split>
I originally used the .html function to set the split directives content, and used $compile service to reconstruct the DOM. This of course destroys the original innerDirective. Since it may be deep, it is not feasible to store it's data in the parent split and retrieve with a link function (although I tried).
I also tried using jquery's .wrap and .unwrap functions, which works, except the added tags aren't compiled as directives, so not really.
Is there a proper way to do this, or a way to compile just the outer tag added with the .wrap, and update the scope of the innerDirective to the new parent?
Thanks for any help and advice you may have!
Edit:
I have a controller along this lies, lets say set to control the split 'E' directive:
function SplitCtrl($scope, $compile){
//stuff
//First way, redefine html contents and recompile
this.split1 = function(){
//element has been linked to the directive's element
$scope.element.html('<split></split><split></split>');
$compile($scope.element.contents())($scope);
};
//Second way, jquery.wrap
this.split2 = function(){
$($scope.element).children('innerDirective').wrap('<split />');
// then I need to instantiate the new split
// without reinstantiating now wrapped directive.
// Adding second split is fine in this case, I can do that.
};
};
The template is:
<innderDirective></innerDirective>

AngularJS : Isolated Scope in directive with Compile in directive

I am struggling to get access to an isolated scope from some html I am building in the compile function of a directive. I have a SIMPLE example, the one I am working with is much more complex. Essentially I can't access properties on the isolated scope. Here is the code.
http://plnkr.co/edit/ETIRs4j3EwZ4DFN5XkPc?p=preview
It's odd to me because the in the console the scope looks great, from the post function.
Notice how the html is set up in the compile function:
tElement.append(angular.element('<div>{{name}}</div><span>{{value}}</span><span>Hello</span>'))
Also notice the second directive, Here with an inherited scope everything works fine.
Edit: I updated the first directive(The directive that isn't working) to set a property in the html so I could look for it in the console when I log the scope. It turns out it is on the $parent scope (The property is named "wat"). But why? It makes sense why it wouldn't work, but I don't understand why that html doesn't have access to the same isolated scope? Is there a way to force the html I am inserting to use the isolated scope instead?
Edit 2: Ok so a lot of questions about why I am trying to do this, the best description could be found here. https://github.com/angular/angular.js/issues/7874. Essentially we wanted to ng-repeat some transcluded content, and expose the item from ng-repeat to the transcluded content, but you can't do that in angular, because the transcluded content only has access to the parent scope.
The goal is to get the first directive to work like the second, in that the value of the two properties (value, and name) work from appending html in the compile function, with an isolated scope.
This is how I would go about what ( I think ) you are trying to achieve
http://plnkr.co/edit/mL7h7Hf8TkkpfsPNoL6g?p=preview
By using a template with transclude: true and a link function you can achieve the desired result as you replace the element with your template while interpreting the angular expressions
You could use replace: true to further customise your directive
app.directive('widget',function(){
return {
restrict:'E',
transclude: true,
template: '<span ng-bind="wat=\'YES\'"></span><div>{{name}}</div><span>{{value}}</span> <span>There</span>',
scope:{
name:'#',
value:'='
},
link:function($scope, tElement){
}
}
});

angular link function iElement magic in angular-ui example

Angular provides the instance element of the current scope within a link function.
The iElement angular passes is capable of doing things beyond a standard HTML element. How does angular do this? Wrap it with jQuery first?
How does the link element in this angular-ui tinymce implementation get the tinymce method e.g. the method shown below on line 49:
elm.tinymce();
I get that the ui-tinymce attribute links to the directive named uiTinymce, and that tinymce is included in the main html, but i don't understand how elm get the method tinymce.
As a reference I've provided the relevant documentation on iElement from http://docs.angularjs.org/guide/directive below.
Linking function:
function link(scope, iElement, iAttrs, controller) { ... }
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.
scope - Scope - The scope to be used by the directive for registering watches.
iElement - instance element - The element where the directive is to be used. It is safe to manipulate the children of the element only in postLink function since the children have already been linked.
From the angular.element page:
Note: All element references in Angular are always wrapped with jQuery
or jqLite; they are never raw DOM references.
So yes, the iElement argument is a jQueryLite, or full jQuery object, which has all of the relevant additional methods that jQuery/jQueryLite provide.
This includes any additional methods that are added via jQuery's plugin mechanisms, which have the effect of being able to call those methods directly on the jQuery object itself.
So the TinyMCE JS file would have code to add in a .tinymce() function to all jQuery objects, which is how the iElement object has it.
Does that make sense?

Categories