Angular performance: ng-show vs. dynamically recompile directive - angularjs

Let's say I have a boolean format that controls how a collection of items should be displayed by my directive. Depending on the value of format, my html changes radically.
So far I'm using two separate templates, and I dynamically recompile the directive whenever format changes, by using $watch(format) in the link function of the directive.
I wonder if it would be faster to use only one template, and split all my code between a big ng-show="format" and a ng-hide="format". What's the best practice in that situation?

$compile is expensive (relatively). Your use case sounds like a candidate for ng-switch, so don't reinvent the wheel if ng-switch works for you.
However, inside your directive, you can cache the compilation of each template and then re-link them by calling the link function returned by $compile. This would be pretty much as performant as ng-switch .
In pseudo code your link function would look like:
link: function(scope, element, attrs) {
//create and cache your template link functions
var format1link = $compile(template1);
var format2link = $compile(template2);
scope.$watch('format') {
//call appropriate link function based on the value of format
var newElement = format1link(scope);
//or
var newElement = format2link(scope);
//replace element contents with newElement
});
}

Related

Angularjs: how to clone an element, uninterpolated

I have a directive that clones its element, passes the clone through $compile(myClone)(scope); and appends it to the DOM.
If the original element has transcluded content, ex: This is some content {{withVariable}}
How can I clone it with the angularjs expressions uninterpolated, so that the cloned element has the expressions (and thus the same dynamic content as the original), rather than the values as resolved at the time of cloning?
If I use ng-bind directive, it work as desired.
ex. This is some content <span ng-bind="withVariable"></span>
Ok, I found a solution using transclude: true on my directive.
And then I have in the link function:
link: function (scope, element, attrs , uiScrollpoint, transclude ){
transclude(scope, function(clone, scope){
// stash the un-interpolated innerHTML
element[0].srcHTML = clone[0].innerHTML;
element.append($compile(clone)(scope));
});
}
When I clone the element, I retrieve the srcHTML before compiling:
var myClone = element.clone();
if(element[0].srcHTML){
myClone[0].innerHTML = element[0].srcHTML;
}
$compile(myClone)(scope);
It seems to work, but I do see the limitation that if the original element's source is modified on the fly by JS DOM-manipulation functions that srcHTML wouldn't stay in sync. I'm thinking that this would be a pretty rare case though.
Maybe there is a better way to do this? If it's possible to get the uninterpolated HTML at clone time rather than only at transclusion time, that would really be the best.

custom Directive replace element not working

I have a custom directive that generates an input with its validation errors , eventually after building the input, here is the part I'm wondering about :
var html= template.get();
element.html(html);
$compile(element)(scope);
I also added to the directive object which I think is not making difference since I don't have template in the object or should it?
replace:true
but yet the directive element DOM is still there , and the generated input is being appended as a child , can you help me out in this ?
Yes, replace is used in conjunction with template/templateUrl.
For the templates that are retrieved dynamically in link, use
var html= template.get();
element.replaceWith($compile(html)(scope));
Notice that obvious drawback in comparison with replace is that directive's attributes won't be translated to template element, this has to be done manually.
Its not working because you haven't provided a template parameter and your link function was manually adding the elements. Remove the link function and add a template parameter like so and it'll work. Ive updated your fiddle with it working too
app.directive('test',function($compile){
return {
restrict:'E',
replace:true,
template:'<input type="text" name="test">'
}
});

Dynamic template depending on dropdown user selection

Trying to use dynamic template that should change depending on user selection.
so when a dropdown option is selected then load another html template.
I like to do this cleanly and in a modular way with controller that can be uni-tested.
I have been reading this
https://coderwall.com/p/onjxng/angular-directives-using-a-dynamic-template
Others include using
ng-include to load the template
Anyone knows of better way to implement?
You can use $templateCache and $compile in your directive. It is not always a better way, depending on what you want to achieve.
link: function (scope, element, attrs) {
$templateCache.get('yourtemplate.html').then(function(tmpl) {
element.html(tmpl);
$compile(element.contents())(scope);
});
}
You get the idea: you can also have a directive acting as a proxy to other directives by not using $templateCache and adding directly to your element the markup for another directive (and compiling it).

Angular.js how to build dynamic template parts using directives with isolated scope

I've got a directive... like so:
.directive('formMenuBuilderMenu', function (formMenuService) {
return {
templateUrl: '../../views/templates/formmenubuilder-menu-template.html',
restrict: 'A',
scope:{
menu:'='
},
link: function postLink(scope, element, attrs) {
// does stuff
} ...
It gets built dynamically using $compile whenever a new menu node is created.
scope.menu = {//new data for menu view directive part}
var $nodeTemplate = '<div form-menu-builder-menu menu="menu"></div>';
var html = $compile($nodeTemplate)(scope);
$content.append(html);
I had the impression that because I've defined a scope section in the formMenuBuilderMenu directive that this directive would have isolate scope and not be affected by new instances created
BUT this doesn't work at all!
What happens is that every time a new directive is created using $compile the scope.menu gets updated using the new value for all previous directives created, not keeping its isolated scope. Indeed logging out the scope in each directive created shows it's the same scope instance every time.
How would I do this so the directive scope remains independent and each instance has it's own scope? Is it even possible? Please let me know if further explanation is needed. I'm sure I'm going about this the wrong way so a pointer in the right direction would be appreciated.
To be clear my main objective is basically to be creating dynamic template parts using directives each with their own subset of data.
Instead of:
var html = $compile($nodeTemplate)(scope);
Try this:
var html = $compile($nodeTemplate)(scope.$new());

Is it recommended or good practice to use a directive that matches standard html tags?

I wanted to write a directive that only applied to IMG tags throughout my whole page, and initially I thought I would have to decorate each tag with a custom directive name, such as:
<img my-img />.
But, while I was putting together some sample code for this question, I decided to see if the directive would match on the element IMG itself. And it worked!
Here's what I did: http://plnkr.co/edit/z4n4a3MN89nRNYyXKCih?p=preview
app.directive('img', function () {
return {
restrict: 'E',
link: function (scope, element) {
element.bind('load', function () {
element.addClass('fadeIn');
});
element.bind('error', function () {
element.removeClass('fadeIn');
});
}
};
});
As you can see, I wanted all images on a page to fade in when they loaded. I wanted to do this in an angular fashion without using jQuery, so I thought this was a good approach, but is it good practice? In my case, I really do want this logic to apply to all the images on my page (and there may be hundreds), so I thought this would be a clean way of doing it, but for the life of me I haven't found anywhere where anyone else does this (i.e., matching a directive to an IMG tag or any standard tag for that matter).
I think I would avoid the img directive. Take note that Angular has already added their own directives which match html element names (e.g. - form, input, select, script), so it seems conceivable that there could potentially be a conflict if they (or any library you use) utilize the same directive name. And do you really want to fade in all images? What if you use an image as a decoration on the page?
It seems like it would be best to instead add the attribute. It's very intuitive with nominal effort. If you don't care about the built in attributes, you could also create your own element (e.g. ).

Resources