Agregar directivas desde la directiva en AngularJS

Resuelto frapontillo asked hace 10 años • 7 respuestas

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 datepickery datepicker-language.ng-required="true"

Si intento agregar esos atributos y luego los uso, $compileobviamente 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 $compileel 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 $compilees 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 selectelemento y, como se esperaba, la compilación se ejecuta dos veces, lo que significa que hay el doble de optionmensajes de correo electrónico esperados.

frapontillo avatar Oct 07 '13 18:10 frapontillo
Aceptado

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 prioritypropiedad 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-thingsen 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: truey 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:truey 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-repeatse 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 ngRepeatcódigo fuente: https://github.com/angular/angular.js/blob/master/src/ng/directive/ngRepeat.js

Khanh TO avatar Oct 07 '2013 15:10 Khanh TO

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 : truey reemplazar el interior con<ng-transclude></ng-transclude>

Espero que te ayude, avísame si algo no está claro.

Alex

mrvdot avatar Oct 09 '2013 15:10 mrvdot