Reputation: 6813
I'm trying to find the best approach for dynamically creating the sub-contents of an element created using a directive.
To give a simple example, say I have directive that say creates a basic div
child element.
<div mydirective></div>
results in
<div mydirective><div></div></div>
The directive might look something like:
angular.module('demo', [])
.directive('mydirective', function() {
return {
restrict: 'A',
template: '<div></div>'
}
})
Then say I wanted to create an "extension" to this directive that makes the inner div
blue. Because I don't know what future extensions I'll need I don't want to switch the template or bind any specific attributes.
I don't know what of these are possible, or what the normal approach is... but can you
1) Add an additional directive to the parent element (bit like css) i.e.
<div mydirective mybluedirective></div>
Where the mybluedirective
looks for the inner div and manipulates it?
or
2) Can you include directive dynamically in the template, e.g.
<div mydirective subdirective="mybluedirective"></div>
angular.module('demo', [])
.directive('mydirective', function() {
return {
restrict: 'A',
template: '<div [[[THE SUBDIRECTIVE PROPERTY FROM SCOPE??]]></div>'
}
})
Is there a way you can intercept pre-compile to inject the directive?
a third option as I've ready is to use a factory and inherited classes to do the work for the directive, but that seems an overkill.
I'm guessing there is an easy way I don't know about, any advice appreciated
EDIT:
I think what I'm trying to do is dynamically modify the template to use another directive before it's compiled.
Upvotes: 3
Views: 826
Reputation: 52867
If you want to dynamically modify a template before compilation, you can use the template function:
HTML:
<div mydirective subdirective="mybluedirective"></div>
JS:
angular.module('demo', [])
.directive('mydirective', function() {
return {
restrict: 'A',
template: function(element, attr) {
return '<div ' + attr.subdirective + '></div>'
}
}
})
Alternatively, you could expose an API from mydirective
that mybluedirective
could use. This solution requires mydirective to delay compilation (because the template will be compiled manually), and more thought as to how mydirective
is designed and intended to be extended:
HTML
<div mydirective mybluedirective></div>
JS
angular.module('demo', [])
.directive('mydirective', function($compile) {
return {
restrict: 'A',
template: '<div></div>',
// we want a child scope so that we don't pollute the parent scope
scope: true,
controller: function($scope, $element) {
var attrs = {};
// expose an API to add attributes dynamically
this.addAttr = function(attr, value) {
attrs[attr] = value;
}
$scope.attrs = attrs;
},
compile: function(element, attr) {
// save the template
var template = element.find('div');
// empty the contents so it is not compiled (we're manually compiling during link)
element.empty();
return function(scope, element, attr) {
// add the attributes to the template
angular.forEach($scope.attrs, function(value, key) {
template.addAttr(key, value);
}
// add the template to the DOM
element.append(template);
// link the template to scope
$compile(template)(scope);
}
}
}
})
.directive('mybluedirective', function() {
return {
restrict: 'A',
require: 'mydirective',
link: function(scope, element, attr, mydirectiveController) {
mydirectiveController.addAttr('ng-class','blue');
}
}
});
Upvotes: 3
Reputation: 39408
I use an ng-class to bind to an exposed variable inside the directive. You'd also need to add a scope, and if you want to set a default, then a controller or link function.
Here is a quick sample:
angular.module('demo', [])
.directive('mydirective', function() {
return {
restrict: 'A',
// add the ng-class to the template. It binds to options.someClass
template: '<div ng-class="options.someClass"></div>',
// give the directive its own Scope with an input object named options
scope: {
options : "="
},
// Give the directive a controller
controller: function($scope) {
// This is an init function to default the input objects
this.onInit = function(){
// If the options object does not exist; create it
if(!$scope.options){
$scope.options = {};
}
// If our custom someClass variable doesn't exist; then give it a default value
if(!$scope.options.someClass){
$scope.options.someClass= "defaultClass";
} }
// call the onInit() function to set up the defaults
onInit();
}
}
})
Then I also provide a CSS as part of the directive containing the default styles. You can put this in practice relatively easily. In your controller create our options object:
myDirectiveOptions = {
someClass = 'myCustomClass';
}
In your view, do this:
<div mydirective options="myDirectiveOptions"></div>
I have found this to be simple, yet very powerful.
Using this approach; to change the directive--ng-class--dynamically; you merely have to update the options object. Somewhere in your controller, you can do:
myDirectiveOptions.someClass = 'someOtherCustomClass';
The ng-class inside of myDirective should pick it up automatically thanks to the magic of Angular binding. This change does not have to be inside myDirective's controller.
Upvotes: 2
Reputation: 586
You can use a second directive that looks for the result of the first one. Just make sure that "mybluedirective" has higher priority than "mydirective".
Js Fiddle: http://jsfiddle.net/XHJC2/
angular.module("demo", []).
controller('SampleCtrl', function ($scope) {
}).
directive('mybluedirective', function () {
return {
priority: 4000,
restrict: 'A',
link: function (scope, elem, attr) {
elem.find('div').html('modified!');
}
};
}).
directive('mydirective', function() {
return {
priority: 40,
restrict: 'A',
template: '<div>TEST</div>'
};
});
Here is doc about "priority" in directives: https://docs.angularjs.org/api/ng/service/$compile#-priority-
Upvotes: 1