Add Directives from Directive in Angularjs

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 with ng-model, the element (in the directive template) needs a name.
  • Secondly, ng-required (or required) 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



Leave a reply



Submit