Add directives from directive in AngularJS
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
Angularjs directive add directives as attribute and bind them dynamically
After a long reading of documentation about AngularJS Directives in various blogs and sites.
I just came to know complete flow of directives
The flow will be from
Compile -> Controller -> preLink -> postLink or (Link)
If you want add any core Directives of angular(ng-model, ng-style,ng-src) at Compile Phase
var app;
app = angular.module('App', []);
app.directive('myDirective', [ '$compile', function($compile) { // Crucial Part return { scope: true, compile: function(element, attrs) { element.attr('ng-src', '{{imageModel}}'); element.attr('ng-click', 'updateImage()'); element.removeAttr('my-directive'); // Crucial Part return { pre: function(scope, ele, attb) {}, post: function(scope, ele, attb) { $compile(ele)(scope); return scope.updateImage = function() { return scope.imageModel = "http://www.google.com/logos/doodles/2015/halet-cambels-99th-birthday-6544342839721984-hp2x.png"; }; } }; }, controller: function($scope, $element, $attrs) { return $scope.imageModel = null; } }; }]);
<!DOCTYPE html><html><head><script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script> <meta charset="utf-8"> <title>JS Bin</title> <style> img { max-width: 100%; } </style></head><body ng-app='App'> <img src="https://www.google.co.in/images/srpr/logo11w.png" alt="Sample Image" my-directive>
</body></html>
How can I dynamically add a directive in AngularJS?
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.
Adding directives to an element using another directive
You need to use $compile
service to achieve this. See this answer.
For your case it should go like this.
angular.module('myModule', [])
.directive('menuItem', function ($compile) {
return {
restrict: 'A',
template: '<a ng-href="{{menuItem.link}}">{{menuItem.name}}</a>',
scope: {
menuItem: '='
},
compile: function (element, attrs) {
element.removeAttr('menu-item');
element.attr('ng-class', '{active : menuItem.isActivated()}');
var fn = $compile(element);
return function(scope){
fn(scope);
};
}
}
});
Adding a Directive to a Controller using Controller as syntax
Check this fork of your plunk: https://plnkr.co/edit/7iA3JMhuUlvIQN9ORs81?p=preview
.directive('myCustomer', function() {
return {
restrict: 'E',
scope: {
customerInfo: '=info'
},
controllerAs: 'vm',
controller: ['$scope', function(scope){
console.log(scope.customerInfo);
}],
templateUrl: 'my-customer-iso.html'
};
});
UPD
code should be like so:
(function(angular) {
'use strict';
angular.module('docsIsolateScopeDirective', [])
.controller('Controller', ['$scope', function($scope) {
$scope.naomi = { name: 'Naomi', address: '1600 Amphitheatre' };
$scope.igor = { name: 'Igor', address: '123 Somewhere' };
}])
.directive('myCustomer', function() {
return {
restrict: 'E',
scope: {
customerInfo: '=info'
},
controllerAs: 'vm',
bindToController: true, //the missing line!!
controller: 'dirCtrl',
templateUrl: 'my-customer-iso.html'
};
})
.controller('dirCtrl', ['$scope', dirCtrl]);
function dirCtrl() {
//var vm = this; //no need in this!
}
})(window.angular);
and
Name: {{vm.customerInfo.name}} Address: {{vm.customerInfo.name}}
in the template
dynamically adding directives in ng-repeat
I know this is an old question, but google brought me here, and I didn't like the answers here... They seemed really complicated for something that should be simple. So I created this directive:
***** NEW CONTENT *****
I've since made this directive more generic, supporting a parsed (the typical angular value) "attributes" attribute.
/**
* Author: Eric Ferreira <http://stackoverflow.com/users/2954747/eric-ferreira> ©2016
*
* This directive takes an attribute object or string and adds it to the element
* before compilation is done. It doesn't remove any attributes, so all
* pre-added attributes will remain.
*
* @param {Object<String, String>?} attributes - object of attributes and values
*/
.directive('attributes', function attributesDirective($compile, $parse) {
'use strict';
return {
priority: 999,
terminal: true,
restrict: 'A',
compile: function attributesCompile() {
return function attributesLink($scope, element, attributes) {
function parseAttr(key, value) {
function convertToDashes(match) {
return match[0] + '-' + match[1].toLowerCase();
}
attributes.$set(key.replace(/([a-z][A-Z])/g, convertToDashes), value !== undefined && value !== null ? value : '');
}
var passedAttributes = $parse(attributes.attributes)($scope);
if (passedAttributes !== null && passedAttributes !== undefined) {
if (typeof passedAttributes === 'object') {
for (var subkey in passedAttributes) {
parseAttr(subkey, passedAttributes[subkey]);
}
} else if (typeof passedAttributes === 'string') {
parseAttr(passedAttributes, null);
}
}
$compile(element, null, 999)($scope);
};
}
};
});
For the OP's use case, you could do:
<li ng-repeat="color in colors">
<span attributes="{'class': color.name}"></span>
</li>
Or to use it as an attribute directive:
<li ng-repeat="color in colors">
<span attributes="color.name"></span>
</li>
***** END NEW CONTENT ******
/**
* Author: Eric Ferreira <http://stackoverflow.com/users/2954747/eric-ferreira> ©2015
*
* This directive will simply take a string directive name and do a simple compilation.
* For anything more complex, more work is needed.
*/
angular.module('attributes', [])
.directive('directive', function($compile, $interpolate) {
return {
template: '',
link: function($scope, element, attributes) {
element.append($compile('<div ' + attributes.directive + '></div>')($scope));
}
};
})
;
For the specific case in this question, one can just rewrite the directive a bit to make it apply the directive to a span by class, as so:
angular.module('attributes', [])
.directive('directive', function($compile, $interpolate) {
return {
template: '',
link: function($scope, element, attributes) {
element.replaceWith($compile('<span class=\"' + attributes.directive + '\"></span>')($scope));
}
};
})
;
Then you can use this anywhere and select a directive by name dynamically. Use it like so:
<li ng-repeat="color in colors">
<span directive="{{color.name}}"></span>
</li>
I purposely kept this directive simple and straightforward. You may (and probably will) have to reword it to fit your needs.
Add custom Directive to existing Input that already has angular directives [ng-model/ng-required]
I initially tried to dynamically add validators to your wk-location-suggest-new
directive by implementing blur
on the input element in combination with ngModel
's $setValidity
method; but don't know what exactly was preventing the event from firing.
Therefore, I turned to the other directive wk-location-suggest-old
and tweaked it a bit to fit in both desired behaviors.
There, I noticed that you were missing a couple of things:
- First of all, in order for a form element to glue with the form itself (
wkProfileCompany
in your case), and to work withng-model
, the element (in the directive template) needs a name.- Secondly,
ng-required
(orrequired
) would work with the form only if it is added as an attribute to the element in the directive template, not the directive which compiles to the template containing the element.
Directive Definition
As you may notice, I've passed two properties from the outer scope to the directive's inner scope, namely:
- the
name
of the input element, - and an
isRequired
flag as to specify whether the input is required or not.
.
.directive('wkLocationSuggestOld', [function () {
return {
restrict: 'E',
require: '?ngModel',
scope: {
name: '@', // <==
isRequired: '=' // <==
},
template: '<input name="{{name}}" type="text" class="{{innerClass}}" ng-model="innerModel"'
+ ' ng-change="onChange()" uib-typeahead="location as row.location for row in typeAhead($viewValue)" '
+ ' typeahead-wait-ms="750" typeahead-on-select="onSelectInternal($item, $model, $label)" '
+ ' typeahead-min-length="2" typeahead-focus-first="true" '
+ ' ng-required="isRequired">', // <== added ng-required here
controller: 'LocationSuggestController',
link: function (scope, element, attrs, ngModel) {
if (!ngModel) {
return;
}
...
}])
HTML
Finally, you can use the tweaked directive in your HTML as such:
<wk-location-suggest-old class="form-control" type="text" name="location2" ng-model="location2" is-required="true"></wk-location-suggest-old>
Plunker
Update
One of the possible reasons for ng-model
not correctly binding in the wk-location-suggest-new
directive to a provided value (i.e. location3
) is that you are replacing the whole DOM
element with a new custom DOM
element which is compiled with the isolated scope
of the directive itself.
Since the directive wk-location-suggest-new
has an isolate scope, the scope is totally unaware of location3
, because location3
(and all the other location values) are defined in the scope of MainCtrl
and NOT the scope of the directive itself; therefore, you'll end up binding the input's value to an undefined
property.
link: function (scope, element, attrs, ngModelCtrl) {
if (!ngModelCtrl) {
return;
}
...
$compile(element)(scope); // <== here
Adding html in a directive that contains directives
var template='<div order-details>Some text</div>'
element.append($compile(template)(scope));
yes it possible to load template using services
$http({method: 'GET', url: '/views/template'})
.success(function(data){
element.append($compile(data)(scope));
});
How to dynamically add a directive to all input elements in AngularJS across controllers & modules
Angular can have multiple directives with the same name, and will apply them all to the element. Create another input
directive with the desired behavior (demo):
.directive('input', function($compile) {
return {
restrict: 'E',
require: '?ngModel',
link: function(scope, elem, attrs, ngModel) {
if (!ngModel) {
return;
}
ngModel.$parsers.push(function(value) {
return value.trim();
});
}
}
});
Related Topics
The Value of "This" Within the Handler Using Addeventlistener
Case Insensitive Regex in JavaScript
Number Prime Test in JavaScript
How to Fire and Forget a Promise in Nodejs (Es7)
What Techniques Can Be Used to Define a Class in JavaScript, and What Are Their Trade-Offs
Check/Uncheck Checkbox with JavaScript
Why Are Callbacks from Promise '.Then' Methods an Anti-Pattern
Difference Between Microtask and MACrotask Within an Event Loop Context
How to Access Parent Iframe from JavaScript
How to Decode a String with Escaped Unicode
When to Use Setattribute VS .Attribute= in JavaScript
Why JavaScript Treats a Number as Octal If It Has a Leading Zero
Defining Methods via Prototype VS Using This in the Constructor - Really a Performance Difference
Sort Array of Objects by Single Key with Date Value
Using Rails 3.1, Where Do You Put Your "Page Specific" JavaScript Code
Why Is Mutating the [[Prototype]] of an Object Bad for Performance