Ajouter les directives de la directive AngularJS
j'essaie de construire une directive qui s'occupe de ajoutant plus de directives à l'élément sur lequel elle est déclarée.
Par exemple, je veux construire une directive qui prend soin d'ajouter datepicker
, datepicker-language
et ng-required="true"
.
Si j'essaie d'ajouter des attributs et ensuite utiliser $compile
j'ai évidemment générer une boucle infinie, donc je vérifie si j'ai déjà ajouté les attributs requis:
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;
}
element.attr('datepicker', 'someValue');
element.attr('datepicker-language', 'en');
// some more
$compile(element)(scope);
}
};
});
bien sûr, si je ne $compile
pas l'élément, les attributs seront définis mais la directive ne sera pas bootstrapped.
cette approche Est-elle correcte ou suis-je tout faux? Est-il un meilleur moyen d'atteindre le même comportement?
UDPATE : étant donné que $compile
est le seul moyen d'y parvenir, y a-t-il un moyen de sauter la première passe de compilation (l'élément peut contenir plusieurs enfants)? Peut-être par terminal:true
?
UPDATE 2 : j'ai essayé de mettre la directive dans un élément select
et, comme prévu, la compilation s'exécute deux fois, ce qui signifie qu'il y a deux fois le nombre prévu de option
.
7 réponses
dans les cas où vous avez plusieurs directives sur un seul élément DOM et où
l'ordre dans lequel ils sont appliqués, vous pouvez utiliser la propriété priority
pour commander leur
application. Les nombres plus élevés courent en premier. La priorité par défaut est 0 si vous n'en spécifiez pas une.
EDIT : après la discussion, voici la solution de travail complète. L'essentiel était de supprimer l'attribut : element.removeAttr("common-things");
, et aussi element.removeAttr("data-common-things");
(dans le cas où les utilisateurs spécifient data-common-things
dans le 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);
}
};
}
};
});
plongeur de travail est disponible à: http://plnkr.co/edit/Q13bUt?p=preview
ou:
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);
}
};
});
explication pourquoi nous devons mettre terminal: true
et priority: 1000
(un nombre élevé):
quand le DOM est prêt, angulaire marche le DOM pour identifier toutes les directives enregistrées et compiler les directives Un par un basé sur priority
si ces directives sont sur le même élément . Nous avons placé la priorité de Notre directive personnalisée à un nombre élevé pour s'assurer qu'elle sera compilée en premier et avec terminal: true
, les autres directives seront sautées après cette directive est compilée.
quand notre coutume la directive est compilée, il va modifier l'élément en ajoutant des directives et en se retirant et utiliser $compile service pour compiler toutes les directives (y compris celles qui ont été sautées) .
si nous ne définissons pas terminal:true
et priority: 1000
, il y a une chance que certaines directives soient compilées avant Notre directive personnalisée. Et quand notre directive personnalisée utilise $compile pour compiler l'élément = > compile à nouveau le directives compilées. Cela provoquera des comportements imprévisibles surtout si les directives compilées avant notre directive custom ont déjà transformé le DOM.
pour plus d'informations sur la priorité et le terminal, consultez comment comprendre le "terminal" de la directive?
un exemple de directive qui modifie également le modèle est ng-repeat
(priorité = 1000), lorsque ng-repeat
est compilé, ng-repeat
faire des copies de l'élément de modèle avant que d'autres directives soient appliquées .
grâce au commentaire de @Izhaki, voici la référence à ngRepeat
code source: https://github.com/angular/angular.js/blob/master/src/ng/directive/ngRepeat.js
vous pouvez réellement gérer tout cela avec juste un template tag simple. Voir http://jsfiddle.net/m4ve9/ pour un exemple. Notez que je n'avais pas besoin d'une propriété de compilation ou de lien sur la définition de super-directive.
pendant le processus de compilation, Angular tire dans les valeurs de template avant de compiler, de sorte que vous pouvez y joindre d'autres directives et Angular s'en chargera pour vous.
S'il s'agit d'un super directive qui doit préserver le contenu interne d'origine, vous pouvez utiliser transclude : true
et remplacer l'intérieur par <ng-transclude></ng-transclude>
Espère que ça aide, laissez-moi savoir si quelque chose n'est pas claire
Alex
Voici une solution qui déplace les directives qui doivent être ajoutées dynamiquement, dans la vue et ajoute également quelques options (de base) conditional-logic. Cela maintient la directive propre sans aucune logique codée.
La directive prend un tableau d'objets, chaque objet contient le nom de la directive, l'ajout de la valeur à transmettre (le cas échéant).
j'avais du mal à trouver une solution pour une directive comme celle-ci jusqu'à ce que je pense que il pourrait être utile d'ajouter de la logique conditionnelle qui n'ajoute qu'une directive basée sur une condition (si la réponse ci-dessous est toujours fictive). J'ai ajouté une propriété optionnelle if
qui devrait contenir une valeur bool, une expression ou une fonction (par exemple définie dans votre controller) qui détermine si la directive doit être ajoutée ou non.
j'utilise aussi attrs.$attr.dynamicDirectives
pour obtenir la déclaration d'attribut exacte utilisée pour ajouter la directive (par exemple data-dynamic-directive
, dynamic-directive
) sans valeurs de chaîne de caractères à vérifier.
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>
j'ai voulu ajouter ma solution puisque celle acceptée ne fonctionnait pas tout à fait pour moi.
je devais ajouter une directive mais aussi garder la mienne sur l'élément.
dans cet exemple, j'ajoute une simple directive de style ng à l'élément. Pour éviter les boucles de compilation infinies et me permettre de conserver ma directive, j'ai ajouté une vérification pour voir si ce que j'avais ajouté était présent avant de recompiler l'élément.
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);
}
}
};
}
};
}]);
Essayez de stocker l'état dans un attribut de l'élément lui-même, comme superDirectiveStatus="true"
par exemple:
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);
}
}
};
});
j'espère que cela vous aide.
il y a eu un changement de 1.3.x à 1.4.x.
En Angle 1.3.x cela a fonctionné:
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();
}
}
maintenant dans L'angle 1.4.x nous devons faire ceci:
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();
}
}
(de la réponse acceptée: https://stackoverflow.com/a/19228302/605586 from Khanh TO).
une solution simple qui pourrait fonctionner dans certains cas est de créer et de compiler un wrapper et d'y ajouter votre élément original.
quelque chose comme...
link: function(scope, elem, attr){
var wrapper = angular.element('<div tooltip></div>');
elem.before(wrapper);
$compile(wrapper)(scope);
wrapper.append(elem);
}
cette solution a l'avantage de garder les choses simples en ne recompilant pas l'élément d'origine.
cela ne fonctionnerait pas si l'une des directives require
de la directive ajoutée était une des directives de l'élément d'origine ou si l'élément d'origine avait positionnement absolu.