una que personalizadas directivas directiva crear javascript angularjs model-view-controller mvvm angularjs-directive

javascript - que - directivas personalizadas angularjs



Agregar directivas de la directiva en AngularJS (7)

Aquí hay una solución que mueve las directivas que deben agregarse dinámicamente, a la vista y también agrega alguna lógica condicional opcional (básica). Esto mantiene la directiva limpia sin lógica codificada.

La directiva toma una matriz de objetos, cada objeto contiene el nombre de la directiva que se agregará y el valor que se le pasará (si corresponde).

Luchaba por pensar en un caso de uso para una directiva como esta hasta que pensé que podría ser útil agregar un poco de lógica condicional que solo agregue una directiva basada en alguna condición (aunque la respuesta a continuación todavía está inventada). Agregué una propiedad opcional if debería contener un valor, expresión o función bool (por ejemplo, definida en su controlador) que determina si la directiva debe agregarse o no.

También estoy usando attrs.$attr.dynamicDirectives para obtener la declaración del atributo exacto que se usa para agregar la directiva (por ejemplo, directiva de data-dynamic-directive dynamic-directive ) sin verificar los valores de la cadena de codificación.

Demo plunker

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>

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.


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


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


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


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.