javascript personalizadas - Agregar directivas de la directiva en AngularJS




crear que (7)

En los casos en los que tenga varias directivas en un solo elemento DOM y el orden en el que se aplican, puede usar la propiedad de priority para ordenar su aplicación. Los números más altos corren primero. La prioridad predeterminada es 0 si no especifica uno.

EDIT : después de la discusión, aquí está la solución de trabajo completa. La clave era eliminar el atributo : element.removeAttr("common-things"); , y también element.removeAttr("data-common-things"); (en caso de que los usuarios 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 de trabajo 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);
      }
    };
  });

DEMO

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 compilar las directivas una por una según la priority si estas directivas se encuentran en el mismo elemento . Establecemos la prioridad de nuestra directiva personalizada a un número alto para asegurarnos de 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ándose a sí mismo, y utilizará el servicio de compilación de $ para compilar todas las directivas (incluidas aquellas que se omitieron) .

Si no establecemos 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 => compile nuevamente las directivas ya compiladas. Esto causará 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 la prioridad y el 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 se compila ng-repeat , ng-repeat hace copias del elemento de la plantilla antes de que se apliquen otras directivas .

Gracias al comentario de @ Izhaki, aquí está la referencia al código fuente de ngRepeat : https://github.com/angular/angular.js/blob/master/src/ng/directive/ngRepeat.js

Estoy tratando de construir una directiva que se encargue de agregar más directivas al elemento en el que se declara. Por ejemplo, quiero crear una directiva que se encargue de agregar datepicker , datepicker-language y ng-required="true" .

Si trato de agregar esos atributos y luego uso $compile , obviamente genero un bucle infinito, así que estoy comprobando si ya he agregado 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 $compile el elemento, los atributos se establecerán pero la directiva no se iniciará.

¿Es correcto este enfoque o lo estoy haciendo mal? ¿Hay una mejor manera de lograr el mismo comportamiento?

UDPATE : dado que $compile es la única forma de lograr esto, ¿hay alguna forma de omitir el primer pase de compilación (el elemento puede contener varios elementos secundarios)? Tal vez estableciendo terminal:true ¿ terminal:true ?

ACTUALIZACIÓN 2 : He intentado colocar la directiva en un elemento de select y, como se esperaba, la compilación se ejecuta dos veces, lo que significa que hay el doble de la cantidad de option esperadas.


Hubo un cambio de 1.3.x a 1.4.x.

En Angular 1.3.x esto funcionó:

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();
    }
}

Ahora en Angular 1.4.x tenemos que hacer esto:

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 respuesta aceptada: https://.com/a/19228302/605586 de Khanh TO).


Intente almacenar el estado en un atributo en el propio elemento, como superDirectiveStatus="true"

Por ejemplo:

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);

        }

      }
    };
  });

Espero que esto te ayude.


Quería agregar mi solución ya que la aceptada no me funcionó del todo.

Necesitaba agregar una directiva pero también mantener la mía en el elemento.

En este ejemplo, estoy agregando una directiva simple de estilo ng al elemento. Para evitar infinitos bucles de compilación y permitirme mantener mi directiva, agregué una verificación para ver si lo que había agregado estaba presente antes de volver a compilar el elemento.

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);
                    }
                }
            };
        }
    };
}]);

En realidad, puedes manejar todo esto con una simple etiqueta de plantilla. Vea http://jsfiddle.net/m4ve9/ para un ejemplo. Tenga en cuenta que en realidad no necesitaba una propiedad de compilación o enlace en la definición de super-directiva.

Durante el proceso de compilación, Angular extrae los valores de la plantilla antes de compilar, por lo que puede adjuntar otras directivas allí y Angular se encargará de usted.

Si esta es una super directiva que necesita preservar el contenido interno original, puede usar transclude : true y reemplazar el interior por <ng-transclude></ng-transclude>

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

Alex


Una solución simple que podría funcionar en algunos casos es crear y compilar un envoltorio y luego agregarle su elemento original.

Algo como...

link: function(scope, elem, attr){
    var wrapper = angular.element('<div tooltip></div>');
    elem.before(wrapper);
    $compile(wrapper)(scope);
    wrapper.append(elem);
}

Esta solución tiene la ventaja de que simplifica las cosas al no volver a compilar el elemento original.

Esto no funcionaría si alguna de las directivas agregadas require alguna de las directivas del elemento original o si el elemento original tiene una posición absoluta.


Obviamente, no hay una verificación periódica de Scope si hay algún cambio en los Objetos adjuntos. No todos los objetos adjuntos al alcance son observados. Alcance prototípicamente mantiene a los vigilantes $$ . Scope solo se itera a través de este $$watchers cuando se llama $digest .

Angular agrega un observador a los observadores de $$ para cada uno de estos

  1. {{expresión}} - En sus plantillas (y en cualquier otro lugar donde haya una expresión) o cuando definamos ng-model.
  2. $ scope. $ watch ('expresión / función') - En su JavaScript solo podemos adjuntar un objeto de alcance para que lo vea angular.

La función $ watch tiene tres parámetros:

  1. La primera es una función de observador que simplemente devuelve el objeto o simplemente podemos agregar una expresión.

  2. La segunda es una función de escucha que se llamará cuando haya un cambio en el objeto. Todas las cosas como los cambios de DOM se implementarán en esta función.

  3. El tercero es un parámetro opcional que toma un booleano. Si es verdadero, el profundo angular observa el objeto y si su falso angular simplemente hace una referencia al objeto. La implementación aproximada de $ watch se ve así

Scope.prototype.$watch = function(watchFn, listenerFn) {
   var watcher = {
       watchFn: watchFn,
       listenerFn: listenerFn || function() { },
       last: initWatchVal  // initWatchVal is typically undefined
   };
   this.$$watchers.push(watcher); // pushing the Watcher Object to Watchers  
};

Hay algo interesante en Angular llamado Digest Cycle. El ciclo $ digest comienza como resultado de una llamada a $ scope. $ Digest (). Supongamos que cambia un modelo de $ scope en una función de controlador a través de la directiva ng-click. En ese caso, AngularJS activa automáticamente un ciclo de $ digest llamando a $ digest (). Además de ng-click, hay otras directivas / servicios incorporados que le permiten cambiar los modelos (por ejemplo, ng-model, $ timeout, etc.) y desencadenar automáticamente un ciclo $ digest. La implementación aproximada de $ digest se ve así.

Scope.prototype.$digest = function() {
      var dirty;
      do {
          dirty = this.$$digestOnce();
      } while (dirty);
}
Scope.prototype.$$digestOnce = function() {
   var self = this;
   var newValue, oldValue, dirty;
   _.forEach(this.$$watchers, function(watcher) {
          newValue = watcher.watchFn(self);
          oldValue = watcher.last;   // It just remembers the last value for dirty checking
          if (newValue !== oldValue) { //Dirty checking of References 
   // For Deep checking the object , code of Value     
   // based checking of Object should be implemented here
             watcher.last = newValue;
             watcher.listenerFn(newValue,
                  (oldValue === initWatchVal ? newValue : oldValue),
                   self);
          dirty = true;
          }
     });
   return dirty;
 };

Si usamos la función setTimeout () de JavaScript para actualizar un modelo de alcance, Angular no tiene forma de saber qué puede cambiar. En este caso, es nuestra responsabilidad llamar a $ apply () manualmente, lo que desencadena un ciclo de $ digest. De manera similar, si tiene una directiva que configura un detector de eventos DOM y cambia algunos modelos dentro de la función del controlador, debe llamar a $ apply () para asegurarse de que los cambios surtan efecto. La gran idea de $ apply es que podemos ejecutar algunos códigos que no son conscientes de Angular, que aún pueden cambiar las cosas en el alcance. Si envolvemos ese código en $ apply, se ocupará de llamar a $ digest (). Implementación aproximada de $ apply ().

Scope.prototype.$apply = function(expr) {
       try {
         return this.$eval(expr); //Evaluating code in the context of Scope
       } finally {
         this.$digest();
       }
};




javascript angularjs model-view-controller mvvm angularjs-directive