Dynamically inclusion of directive in the view - angularjs

Let's say I have the following directives:
myDirectiveA
myDirectiveB
...
and I have a variable (module) which can be a or b, ... I want to dynamically display the directives depending on module.
I know I could do
<div class="my-directives-a" ng-show="module == 'a'"></div>
<div class="my-directives-b" ng-show="module == 'b'"></div>
but this is not exactly what I want. On my project I'd like to have something like this
<div ng-repeat="module in modules">
<div class="my-directive-{{ module }}" ...></div>
</div>
So I created this plunker script to check my idea but that doesn't seem to work :(
My question are: is this possible and if so how? And is this a good idea in the first place or should I try to solve my problem in a different way?

One option to accomplish dynamic directives is through the use of $compile.
Given a directive:
angular.module(...)
.directive("dynamicDirective", ["$compile", function($compile) {
return {
scope: { dirName: "=" },
link: function(scope, elem, attrs) {
var template = '<div class="my-directives-"' + scope.dirName + '></div>';
directiveElem = $compile(template);
elem.append(directiveElem);
}
};
}]);
You can use the directive as follows:
<div dynamic-directive dir-name="module" ng-repeat="module in modules"></div>

Related

angularJS - variable component templates

Is it possible to define a variable template for a component in angularJS 1.6?
Something like this:
<div class="test">
<{{$ctrl.GetElement()}}>
</div>
for cases in which I want to decide in runtime what the template be like.
Is there a way to do it?
Here is a simple example of a "variable template" using $compile. Let's define a "generator" directive which will be able to generate other directives:
app.directive('createDirective', function($compile) {
return {
scope: {
directiveName: '#'
},
link: function(scope, element) {
var newHtml = '<' + scope.directiveName +'></ '+ scope.directiveName +'>';
element.append($compile(newHtml)(scope));
}
};
});
This "generator" directive takes in a string (via the attribute "directive-name"), assembles new HTML, compiles it, and appends the resulting HTML to the generator directive.
I've defined a separate directive named "Hello", which I want to be called dynamically from the generator directive:
app.directive('hello', function() {
return {
restrict: 'E',
link: function(scope, element) {
element.append("Hello!");
}
}
});
Now, we can use the generator directive to compile the "Hello" directive
<div create-directive directive-name="hello"></div>
which results in this generated HTML
<hello class="ng-scope">
<!-- hello-->
Hello!
</hello>
In addition, we can pass a variable from a controller to the generator directive in a similar way:
app.controller('MainCtrl', function($scope) {
$scope.newDirective = "from-controller";
});
And in the HTML:
<div create-directive directive-name="{{newDirective}}"></div>
Be sure to take a look at the $compile documentation.
Demo

How to use a custom directive inside a custom directive with require

I am quite fairly new to AngularJS so I hope it's quite understandable why I'm having difficulty with this problem.
First I created a custom directive which uses another custom directive angular plugin http://vitalets.github.io/angular-xeditable/
'use strict';
require('../../../../vendor/angular-xeditable/xeditable.js');
angular.module('ft.core').directive('ftXeditableText', function() {
var result = {
restrict: 'E',
scope: {
item: "="
},
templateUrl: 'views/EditableText.html',
link: function (scope, element, attrs) {
}
};
return result;
});
And this is my template file EditableText.html
<span editable-text="{{item.Name}}">
{{item.Name}}
</span>
In my html code, it renders the resulting tag
<ft-xeditable-text item="ngModel[0]" class="ng-scope ng-isolate-scope">
<span editable-text="XEditable Text" class="ng-binding">
XEditable Text
</span>
</ft-xeditable-text>
I can't seem to make the plugin work. It renders in the browser but the plugin's feature doesn't execute. How should I approach this? Please help me. Thanks

Different scope when using ng-repeat and custom directive in one element

I have a directive like:
angular.module('myApp').directive('myDirective', function() {
return {
templateUrl: '/views/myView.html',
restrict: 'E',
link: function (scope, element, attrs) {
console.log(scope);
console.log(angular.element(element).scope());
}
};
});
the template html is:
<div>{{item.text}}</div>
and view html is:
<div ng-init="items=[{text:'hello'}, {text: 'world'}]">
<my-directive ng-repeat="item in items"></my-directive>
</div>
I find the scope and angular.element(element).scope() is not the same one, but I have to use angular.element(element).scope() way to get the scope of ngRepeat-item somewhere else.
Am I misunderstanding something?
Note:
The code could be reproduced only when including jQuery.
Here is related github issue.
The main reason is: angular1.2.x does not support jQuery2.0.x, and angular1.3.x will do.
So the solution here will be:
angular1.2.x + jQuery 1.x
angular1.3.x + jQuery 2.x
angularjs only

Detect if a transclude content has been given for a angularjs directive

I have a directive (a progressbar) which should have two possible states, one without any description and one with a label on the left side.
It would be cool to simple use the transcluded content for this label.
Does anyone know how I can add a class to my directive depending whether a transclude content has been given or not?
So I want to add:
<div class="progress" ng-class="{withLabel: *CODE GOES HERE*}">
<div class="label"><span ng-transclude></span>
<div class="other">...</div>
</div>
Thanks a lot!
After release of Angular v1.5 with multi-slot transclusion it's even simpler. For example you have used component instead of directive and don't have access to link or compile functions. Yet you have access to $transclude service. So you can check presence of content with 'official' method:
app.component('myTransclude', {
transclude: {
'slot': '?transcludeSlot'
},
controller: function ($transclude) {
this.transcludePresent = function() {
return $transclude.isSlotFilled('slot');
};
}
})
with template like this:
<div class="progress" ng-class="{'with-label': withLabel}">
<div class="label"><span ng-transclude="slot"></span>
<div class="other">...</div>
</div>
Based on #Ilan's solution, you can use this simple $transclude function to know if there is transcluded content or not.
$transclude(function(clone){
if(clone.length){
scope.hasTranscluded = true;
}
});
Plnkr demonstrating this approach with ng-if to set default content if nothing to transclude: http://plnkr.co/hHr0aoSktqZYKoiFMzE6
Here is a plunker: http://plnkr.co/edit/ednJwiceWD5vS0orewKW?p=preview
You can find the transcluded element inside the linking function and check it's contents:
Directive:
app.directive('progressbar', function(){
return {
scope: {},
transclude: true,
templateUrl: "progressbar.html",
link: function(scope,elm){
var transcluded = elm.find('span').contents();
scope.withLabel = transcluded.length > 0; // true or false
}
}
})
Template:
<div class="progress" ng-class="{'with-label': withLabel}">
<div class="label"><span ng-transclude></span>
<div class="other">...</div>
</div>
You can also create your custom transclusion directive like so:
app.directive('myTransclude', function(){
return {
link: function(scope, elm, attrs, ctrl, $transclude){
$transclude(function(clone){
// Do something with this:
// if(clone.length > 0) ...
elm.empty();
elm.append(clone);
})
}
}
})
Based on the solution from #plong0 & #Ilan, this seems to work a bit better since it works with whitespace as well.
$transcludeFn(function(clonedElement) {
scope.hasTranscludedContent = clonedElement.html().trim() === "";
});
where previously <my-directive> </my-directive> would return that it has a .length of 1 since it contains a text node. since the function passed into $transcludeFn returns a jQuery object of the contents of the transcluded content, we can just get the inner text, remove whitespace on the ends, and check to see if it's blank or not.
Note that this only checks for text, so including html elements without text will also be flagged as empty. Like this: <my-directive> <span> </span> </my-directive> - This worked for my needs though.

ng-repeat in combination with custom directive

I'm having an issue with using the ng-repeat directive in combination with my own custom directive.
HTML:
<div ng-app="myApp">
<x-template-field x-ng-repeat="field in ['title', 'body']" />
</div>
JS:
angular.module('myApp', [])
.directive('templateField', function () {
return {
restrict: 'E',
compile: function(element, attrs, transcludeFn) {
element.replaceWith('<input type="text" />');
}
};
});
See jSFiddle
The problem here is that nothing is replaced. What I'm trying to accomplish is an output of 2x input fields, with the 'x-template-field' tags completely replaced in the DOM. My suspicion is that since ng-repeat is modifying the DOM at the same time, this won't work.
According to this Stack Overflow question, the accepted answer seems to indicate this has actually worked in earlier versions of AngularJS (?).
Wouldn't element.html('...') work?
While element.html('...') actually injects the generated HTML into the target element, I do not want the HTML as a child element of the template tag, but rather replace it completely in the DOM.
Why don't I wrap my template tag with another tag that has the ng-repeat directive?
Basically, for the same reason as above, I don't want my generated HTML as a child element to the repeating tag. While it would probably work decently in my application, I would still feel like I've adapted my markup to fit Angular and not the other way around.
Why am I not using the 'template' property?
I haven't found any way to alter the HTML retrieved from the 'template' / 'templateUrl' properties. The HTML I want to inject is not static, it's dynamically generated from external data.
Am I too picky with my markup?
Probably. :-)
Any help is appreciated.
Your directive needs to run before ng-repeat by using a higher priority, so when ng-repeat clones the element it is able to pick your modifications.
The section "Reasons behind the compile/link separation" from the Directives user guide have an explanation on how ng-repeat works.
The current ng-repeat priority is 1000, so anything higher than this should do it.
So your code would be:
angular.module('myApp', [])
.directive('templateField', function () {
return {
restrict: 'E',
priority: 1001, <-- PRIORITY
compile: function(element, attrs, transcludeFn) {
element.replaceWith('<input type="text" />');
}
};
});
Put your ng-repeat in the template. You could modify attributes of element and accordingly in directive to determine if ng-repeat is needed, or what data to use inside the directive compiling
HTML(attribute):
<div ng-app="myApp" template-field></div>
JS:
angular.module('myApp', [])
.directive('templateField', function () {
return {
restrict: 'A',
template:'<input type="text" value="{{field}" ng-repeat="field in [\'title\',\'body\']" />'
};
});
DEMO: http://jsfiddle.net/GDfxd/3/
Also works as an element :
HTML(element):
<div ng-app="myApp" >
<template-field/>
</div>
JS
angular.module('myApp', [])
.directive('templateField', function () {
return {
restrict: 'E',
replace:true,
template:'<input type="text" value="{{field}}" ng-repeat="field in [\'title\',\'body\']" />'
};
});
DEMO: http://jsfiddle.net/GDfxd/3/

Resources