Agregar directivas desde la directiva en AngularJS
Estoy intentando crear una directiva que se encargue de agregar más directivas al elemento en el que está declarado. Por ejemplo, quiero crear una directiva que se encargue de agregar datepicker
y datepicker-language
.ng-required="true"
Si intento agregar esos atributos y luego los uso, $compile
obviamente genero un bucle infinito, así que estoy verificando si ya agregué los atributos necesarios:
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);
}
};
});
Por supuesto, si no elevo $compile
el elemento, los atributos se establecerán pero la directiva no se iniciará.
¿Es este enfoque correcto o lo estoy haciendo mal? ¿Existe una mejor manera de lograr el mismo comportamiento?
UDPATE : dado que $compile
es la única forma de lograr esto, ¿hay alguna manera de omitir el primer paso de compilación (el elemento puede contener varios elementos secundarios)? ¿Quizás estableciendo terminal:true
?
ACTUALIZACIÓN 2 : Intenté poner la directiva en un select
elemento y, como se esperaba, la compilación se ejecuta dos veces, lo que significa que hay el doble de option
mensajes de correo electrónico esperados.
En los casos en los que tenga varias directivas en un único elemento DOM y en los que el orden en el que se aplican sea importante, puede utilizar la priority
propiedad para ordenar su aplicación. Los números más altos corren primero. La prioridad predeterminada es 0 si no especifica ninguna.
EDITAR : después de la discusión, aquí está la solución funcional completa. La clave fue eliminar el atributo : element.removeAttr("common-things");
y también element.removeAttr("data-common-things");
(en caso de que los usuarios lo especifiquen data-common-things
en el 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);
}
};
}
};
});
El plunker en funcionamiento está disponible en: http://plnkr.co/edit/Q13bUt?p=preview
O:
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);
}
};
});
MANIFESTACIÓN
Explicación de por qué tenemos que configurar terminal: true
y priority: 1000
(un número alto):
Cuando el DOM está listo, angular recorre el DOM para identificar todas las directivas registradas y compila las directivas una por una en función de priority
si estas directivas están en el mismo elemento . Establecemos la prioridad de nuestra directiva personalizada en un número alto para garantizar que se compilará primero y con terminal: true
, las otras directivas se omitirán después de compilar esta directiva.
Cuando se compila nuestra directiva personalizada, modificará el elemento agregando directivas y eliminándolas y usará el servicio $compile para compilar todas las directivas (incluidas las que se omitieron). .
Si no configuramos terminal:true
y priority: 1000
, existe la posibilidad de que algunas directivas se compilen antes de nuestra directiva personalizada. Y cuando nuestra directiva personalizada usa $compile para compilar el elemento => compila nuevamente las directivas ya compiladas. Esto provocará un comportamiento impredecible, especialmente si las directivas compiladas antes de nuestra directiva personalizada ya han transformado el DOM.
Para obtener más información sobre prioridad y terminal, consulte ¿Cómo entender el "terminal" de la directiva?
Un ejemplo de una directiva que también modifica la plantilla es ng-repeat
(prioridad = 1000), cuando ng-repeat
se compila, ng-repeat
haga copias del elemento de la plantilla antes de que se apliquen otras directivas. .
Gracias al comentario de @Izhaki, aquí está la referencia al ngRepeat
código fuente: https://github.com/angular/angular.js/blob/master/src/ng/directive/ngRepeat.js
De hecho, puedes manejar todo esto con solo una simple etiqueta de plantilla. Consulte http://jsfiddle.net/m4ve9/ para ver un ejemplo. Tenga en cuenta que en realidad no necesitaba una propiedad de compilación o enlace en la definición de superdirectiva.
Durante el proceso de compilación, Angular extrae los valores de la plantilla antes de compilar, por lo que puede adjuntar más directivas allí y Angular se encargará de ello por usted.
Si se trata de una superdirectiva que necesita preservar el contenido interno original, puede usar transclude : true
y reemplazar el interior con<ng-transclude></ng-transclude>
Espero que te ayude, avísame si algo no está claro.
Alex