The best way to pass an object to an angular directive is by using the &. When you use &, angular compiles the string as an expression and sets the scope variable in your directive to a function that, when called, will evaluate the expression in the context of the directive's parent's scope.
AngularJS allows you to create custom directives with which it becomes easier to encapsulate and simplify DOM manipulation in AngularJS. These directives extend the HTML functionality.
directive. ts file in src/app folder and add the code snippet below. Here, we are importing Directive and ElementRef from Angular core. The Directive provides the functionality of @Directive decorator in which we provide its property selector to appHighLight so that we can use this selector anywhere in the application.
Custom directives are used in AngularJS to extend the functionality of HTML. Custom directives are defined using "directive" function. A custom directive simply replaces the element for which it is activated.
In cases where you have multiple directives on a single DOM element and where the
order in which they’re applied matters, you can use the priority
property to order their
application. Higher numbers run first. The default priority is 0 if you don’t specify one.
EDIT: after the discussion, here's the complete working solution. The key was to remove the attribute: element.removeAttr("common-things");
, and also element.removeAttr("data-common-things");
(in case users specify data-common-things
in the html)
angular.module('app')
.directive('commonThings', function ($compile) {
return {
restrict: 'A',
replace: false,
terminal: true, //this setting is important, see explanation below
priority: 1000, //this setting is important, see explanation below
compile: function compile(element, attrs) {
element.attr('tooltip', '{{dt()}}');
element.attr('tooltip-placement', 'bottom');
element.removeAttr("common-things"); //remove the attribute to avoid indefinite loop
element.removeAttr("data-common-things"); //also remove the same attribute with data- prefix in case users specify data-common-things in the html
return {
pre: function preLink(scope, iElement, iAttrs, controller) { },
post: function postLink(scope, iElement, iAttrs, controller) {
$compile(iElement)(scope);
}
};
}
};
});
Working plunker is available at: http://plnkr.co/edit/Q13bUt?p=preview
Or:
angular.module('app')
.directive('commonThings', function ($compile) {
return {
restrict: 'A',
replace: false,
terminal: true,
priority: 1000,
link: function link(scope,element, attrs) {
element.attr('tooltip', '{{dt()}}');
element.attr('tooltip-placement', 'bottom');
element.removeAttr("common-things"); //remove the attribute to avoid indefinite loop
element.removeAttr("data-common-things"); //also remove the same attribute with data- prefix in case users specify data-common-things in the html
$compile(element)(scope);
}
};
});
DEMO
Explanation why we have to set terminal: true
and priority: 1000
(a high number):
When the DOM is ready, angular walks the DOM to identify all registered directives and compile the directives one by one based on priority
if these directives are on the same element. We set our custom directive's priority to a high number to ensure that it will be compiled first and with terminal: true
, the other directives will be skipped after this directive is compiled.
When our custom directive is compiled, it will modify the element by adding directives and removing itself and use $compile service to compile all the directives (including those that were skipped).
If we don't set terminal:true
and priority: 1000
, there is a chance that some directives are compiled before our custom directive. And when our custom directive uses $compile to compile the element => compile again the already compiled directives. This will cause unpredictable behavior especially if the directives compiled before our custom directive have already transformed the DOM.
For more information about priority and terminal, check out How to understand the `terminal` of directive?
An example of a directive that also modifies the template is ng-repeat
(priority = 1000), when ng-repeat
is compiled, ng-repeat
make copies of the template element before other directives get applied.
Thanks to @Izhaki's comment, here is the reference to ngRepeat
source code: https://github.com/angular/angular.js/blob/master/src/ng/directive/ngRepeat.js
You can actually handle all of this with just a simple template tag. See http://jsfiddle.net/m4ve9/ for an example. Note that I actually didn't need a compile or link property on the super-directive definition.
During the compilation process, Angular pulls in the template values before compiling, so you can attach any further directives there and Angular will take care of it for you.
If this is a super directive that needs to preserve the original internal content, you can use transclude : true
and replace the inside with <ng-transclude></ng-transclude>
Hope that helps, let me know if anything is unclear
Alex
Here's a solution that moves the directives that need to be added dynamically, into the view and also adds some optional (basic) conditional-logic. This keeps the directive clean with no hard-coded logic.
The directive takes an array of objects, each object contains the name of the directive to be added and the value to pass to it (if any).
I was struggling to think of a use-case for a directive like this until I thought that it might be useful to add some conditional logic that only adds a directive based on some condition (though the answer below is still contrived). I added an optional if
property that should contain a bool value, expression or function (e.g. defined in your controller) that determines if the directive should be added or not.
I'm also using attrs.$attr.dynamicDirectives
to get the exact attribute declaration used to add the directive (e.g. data-dynamic-directive
, dynamic-directive
) without hard-coding string values to check for.
Plunker Demo
angular.module('plunker', ['ui.bootstrap'])
.controller('DatepickerDemoCtrl', ['$scope',
function($scope) {
$scope.dt = function() {
return new Date();
};
$scope.selects = [1, 2, 3, 4];
$scope.el = 2;
// For use with our dynamic-directive
$scope.selectIsRequired = true;
$scope.addTooltip = function() {
return true;
};
}
])
.directive('dynamicDirectives', ['$compile',
function($compile) {
var addDirectiveToElement = function(scope, element, dir) {
var propName;
if (dir.if) {
propName = Object.keys(dir)[1];
var addDirective = scope.$eval(dir.if);
if (addDirective) {
element.attr(propName, dir[propName]);
}
} else { // No condition, just add directive
propName = Object.keys(dir)[0];
element.attr(propName, dir[propName]);
}
};
var linker = function(scope, element, attrs) {
var directives = scope.$eval(attrs.dynamicDirectives);
if (!directives || !angular.isArray(directives)) {
return $compile(element)(scope);
}
// Add all directives in the array
angular.forEach(directives, function(dir){
addDirectiveToElement(scope, element, dir);
});
// Remove attribute used to add this directive
element.removeAttr(attrs.$attr.dynamicDirectives);
// Compile element to run other directives
$compile(element)(scope);
};
return {
priority: 1001, // Run before other directives e.g. ng-repeat
terminal: true, // Stop other directives running
link: linker
};
}
]);
<!doctype html>
<html ng-app="plunker">
<head>
<script src="//code.angularjs.org/1.2.20/angular.js"></script>
<script src="//angular-ui.github.io/bootstrap/ui-bootstrap-tpls-0.6.0.js"></script>
<script src="example.js"></script>
<link href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/css/bootstrap-combined.min.css" rel="stylesheet">
</head>
<body>
<div data-ng-controller="DatepickerDemoCtrl">
<select data-ng-options="s for s in selects" data-ng-model="el"
data-dynamic-directives="[
{ 'if' : 'selectIsRequired', 'ng-required' : '{{selectIsRequired}}' },
{ 'tooltip-placement' : 'bottom' },
{ 'if' : 'addTooltip()', 'tooltip' : '{{ dt() }}' }
]">
<option value=""></option>
</select>
</div>
</body>
</html>
I wanted to add my solution since the accepted one didn't quite work for me.
I needed to add a directive but also keep mine on the element.
In this example I am adding a simple ng-style directive to the element. To prevent infinite compile loops and allowing me to keep my directive I added a check to see if what I added was present before recompiling the element.
angular.module('some.directive', [])
.directive('someDirective', ['$compile',function($compile){
return {
priority: 1001,
controller: ['$scope', '$element', '$attrs', '$transclude' ,function($scope, $element, $attrs, $transclude) {
// controller code here
}],
compile: function(element, attributes){
var compile = false;
//check to see if the target directive was already added
if(!element.attr('ng-style')){
//add the target directive
element.attr('ng-style', "{'width':'200px'}");
compile = true;
}
return {
pre: function preLink(scope, iElement, iAttrs, controller) { },
post: function postLink(scope, iElement, iAttrs, controller) {
if(compile){
$compile(iElement)(scope);
}
}
};
}
};
}]);
Try storing the state in a attribute on the element itself, such as superDirectiveStatus="true"
For example:
angular.module('app')
.directive('superDirective', function ($compile, $injector) {
return {
restrict: 'A',
replace: true,
link: function compile(scope, element, attrs) {
if (element.attr('datepicker')) { // check
return;
}
var status = element.attr('superDirectiveStatus');
if( status !== "true" ){
element.attr('datepicker', 'someValue');
element.attr('datepicker-language', 'en');
// some more
element.attr('superDirectiveStatus','true');
$compile(element)(scope);
}
}
};
});
I hope this helps you.
There was a change from 1.3.x to 1.4.x.
In Angular 1.3.x this worked:
var dir: ng.IDirective = {
restrict: "A",
require: ["select", "ngModel"],
compile: compile,
};
function compile(tElement: ng.IAugmentedJQuery, tAttrs, transclude) {
tElement.append("<option value=''>--- Kein ---</option>");
return function postLink(scope: DirectiveScope, element: ng.IAugmentedJQuery, attributes: ng.IAttributes) {
attributes["ngOptions"] = "a.ID as a.Bezeichnung for a in akademischetitel";
scope.akademischetitel = AkademischerTitel.query();
}
}
Now in Angular 1.4.x we have to do this:
var dir: ng.IDirective = {
restrict: "A",
compile: compile,
terminal: true,
priority: 10,
};
function compile(tElement: ng.IAugmentedJQuery, tAttrs, transclude) {
tElement.append("<option value=''>--- Kein ---</option>");
tElement.removeAttr("tq-akademischer-titel-select");
tElement.attr("ng-options", "a.ID as a.Bezeichnung for a in akademischetitel");
return function postLink(scope: DirectiveScope, element: ng.IAugmentedJQuery, attributes: ng.IAttributes) {
$compile(element)(scope);
scope.akademischetitel = AkademischerTitel.query();
}
}
(From the accepted answer: https://stackoverflow.com/a/19228302/605586 from Khanh TO).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With