I have a very boiled down version of what I am doing that gets the problem across.
I have a simple directive
. Whenever you click an element, it adds another one. However, it needs to be compiled first in order to render it correctly.
My research led me to $compile
. But all the examples use a complicated structure that I don't really know how to apply here.
Fiddles are here: http://jsfiddle.net/paulocoelho/fBjbP/1/
And the JS is here:
var module = angular.module('testApp', [])
.directive('test', function () {
return {
restrict: 'E',
template: '<p>{{text}}</p>',
scope: {
text: '@text'
},
link:function(scope,element){
$( element ).click(function(){
// TODO: This does not do what it's supposed to :(
$(this).parent().append("<test text='n'></test>");
});
}
};
});
Solution by Josh David Miller: http://jsfiddle.net/paulocoelho/fBjbP/2/
You have a lot of pointless jQuery in there, but the $compile service is actually super simple in this case:
.directive( 'test', function ( $compile ) {
return {
restrict: 'E',
scope: { text: '@' },
template: '<p ng-click="add()">{{text}}</p>',
controller: function ( $scope, $element ) {
$scope.add = function () {
var el = $compile( "<test text='n'></test>" )( $scope );
$element.parent().append( el );
};
}
};
});
You'll notice I refactored your directive too in order to follow some best practices. Let me know if you have questions about any of those.
In addition to perfect Riceball LEE's example of adding a new element-directive
newElement = $compile("<div my-directive='n'></div>")($scope)
$element.parent().append(newElement)
Adding a new attribute-directive to existed element could be done using this way:
Let's say you wish to add on-the-fly my-directive
to the span
element.
template: '<div>Hello <span>World</span></div>'
link: ($scope, $element, $attrs) ->
span = $element.find('span').clone()
span.attr('my-directive', 'my-directive')
span = $compile(span)($scope)
$element.find('span').replaceWith span
Hope that helps.
Maximum call stack size exceeded
error always happens because of infinite recursion. I've never seen an instance where increasing the stack size would solve it.
Dynamically adding directives on angularjs has two styles:
Add an angularjs directive into another directive
inserting a new element(directive)
inserting a new attribute(directive) to element
inserting a new element(directive)
it's simple. And u can use in "link" or "compile".
var newElement = $compile( "<div my-diretive='n'></div>" )( $scope );
$element.parent().append( newElement );
inserting a new attribute to element
It's hard, and make me headache within two days.
Using "$compile" will raise critical recursive error!! Maybe it should ignore the current directive when re-compiling element.
$element.$set("myDirective", "expression");
var newElement = $compile( $element )( $scope ); // critical recursive error.
var newElement = angular.copy(element); // the same error too.
$element.replaceWith( newElement );
So, I have to find a way to call the directive "link" function. It's very hard to get the useful methods which are hidden deeply inside closures.
compile: (tElement, tAttrs, transclude) ->
links = []
myDirectiveLink = $injector.get('myDirective'+'Directive')[0] #this is the way
links.push myDirectiveLink
myAnotherDirectiveLink = ($scope, $element, attrs) ->
#....
links.push myAnotherDirectiveLink
return (scope, elm, attrs, ctrl) ->
for link in links
link(scope, elm, attrs, ctrl)
Now, It's work well.
function addAttr(scope, el, attrName, attrValue) {
el.replaceWith($compile(el.clone().attr(attrName, attrValue))(scope));
}
The accepted answer by Josh David Miller works great if you are trying to dynamically add a directive that uses an inline template
. However if your directive takes advantage of templateUrl
his answer will not work. Here is what worked for me:
.directive('helperModal', [, "$compile", "$timeout", function ($compile, $timeout) {
return {
restrict: 'E',
replace: true,
scope: {},
templateUrl: "app/views/modal.html",
link: function (scope, element, attrs) {
scope.modalTitle = attrs.modaltitle;
scope.modalContentDirective = attrs.modalcontentdirective;
},
controller: function ($scope, $element, $attrs) {
if ($attrs.modalcontentdirective != undefined && $attrs.modalcontentdirective != '') {
var el = $compile($attrs.modalcontentdirective)($scope);
$timeout(function () {
$scope.$digest();
$element.find('.modal-body').append(el);
}, 0);
}
}
}
}]);
Josh David Miller is correct.
PCoelho, In case you're wondering what $compile
does behind the scenes and how HTML output is generated from the directive, please take a look below
The $compile
service compiles the fragment of HTML("< test text='n' >< / test >"
) that includes the directive("test" as an element) and produces a function. This function can then be executed with a scope to get the "HTML output from a directive".
var compileFunction = $compile("< test text='n' > < / test >");
var HtmlOutputFromDirective = compileFunction($scope);
More details with full code samples here: http://www.learn-angularjs-apps-projects.com/AngularJs/dynamically-add-directives-in-angularjs
Inspired from many of the previous answers I have came up with the following "stroman" directive that will replace itself with any other directives.
app.directive('stroman', function($compile) {
return {
link: function(scope, el, attrName) {
var newElem = angular.element('<div></div>');
// Copying all of the attributes
for (let prop in attrName.$attr) {
newElem.attr(prop, attrName[prop]);
}
el.replaceWith($compile(newElem)(scope)); // Replacing
}
};
});
Important: Register the directives that you want to use with restrict: 'C'
. Like this:
app.directive('my-directive', function() {
return {
restrict: 'C',
template: 'Hi there',
};
});
You can use like this:
<stroman class="my-directive other-class" randomProperty="8"></stroman>
To get this:
<div class="my-directive other-class" randomProperty="8">Hi there</div>
Protip. If you don't want to use directives based on classes then you can change '<div></div>'
to something what you like. E.g. have a fixed attribute that contains the name of the desired directive instead of class
.
Success story sharing
$compile
? Thanks for your answer by the way!$compile
service is what wires directives up and hooks them into the event cycle. There's no way around$compile
ing in a situation like this, but in most cases another directive like ngRepeat can accomplish the same job (so ngRepeat is doing the compiling for us). Do you have a specific use case?