198 votes

Ajouter des directives de la directive dans AngularJS

Je suis en train de construire une directive qui prend soin d' ajouter plus de directives à l'élément qu'il est déclaré. Par exemple, je veux construire une directive qui prend soin de l'ajout d' 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 n'ai pas l' $compile l'élément, les attributs seront ensemble, mais la directive ne sera pas démarré.

Cette approche est-elle correcte ou suis-je tout faux? Est-il un meilleur moyen d'atteindre le même comportement?

UDPATE: compte tenu du fait qu' $compile est la seule façon d'atteindre cet objectif, il est un moyen de sauter la première compilation pass (l'élément peut contenir plusieurs enfants)? Peut-être par la mise en terminal:true?

Mise à JOUR 2: j'ai essayé de mettre la directive en select élément et, comme prévu, la compilation se déroule deux fois, ce qui signifie qu'il est deux fois le nombre attendu de l' options.

264voto

Khanh TO Points 21297

Dans le cas où vous avez plusieurs directives sur un seul élément du DOM et où l' l'ordre dans lequel ils sont appliqués des questions, vous pouvez utiliser l' priority de la propriété à l'ordre de leur application. Les numéros les plus élevés exécuter en premier. La priorité par défaut est 0 si vous ne spécifiez pas un.

EDIT: après discussion, voici la complète solution de travail. La clé a été de supprimer l'attribut: element.removeAttr("common-things");, et également element.removeAttr("data-common-things"); (dans le cas des utilisateurs de spécifier data-common-things dans le code 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);
          }
        };
      }
    };
  });

Travail plunker 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);
      }
    };
  });

DÉMO

Explication des raisons pour lesquelles nous avons terminal: true et priority: 1000 (un grand nombre):

Lorsque le DOM est prêt, angulaire marche dans les DOM pour identifier tous les inscrits de directives et de compiler les directives basée sur priority si ces directives sont sur le même élément. Nous avons mis notre coutume de la directive en priorité à un nombre élevé afin de s'assurer qu'il sera compilé en premier et avec terminal: true, les autres directives seront ignorés après cette directive est compilé.

Lors de notre coutume directive est compilé, il va modifier l'élément par l'ajout de directives et de suppression de lui-même et d'utiliser $compiler service pour compiler toutes les directives (y compris ceux qui ont été ignorés).

Si nous ne fixons pas les terminal:true et priority: 1000, il est possible que certaines directives sont compilés avant notre coutume directive. Et quand notre coutume directive utilise $compiler compiler l'élément => compiler à nouveau la déjà établi des directives. Cela va provoquer un comportement imprévisible, surtout si les directives compilé avant notre coutume directive ont déjà transformé les DOM.

Pour plus d'informations à propos de la priorité et de terminal, découvrez Comment comprendre le "terminal" de la directive?

Un exemple d'une directive qui modifie également le modèle est - ng-repeat (priorité = 1000), lors de l' ng-repeat est compilé, ng-repeat faire des copies de l'élément de modèle, avant que d'autres directives appliquées.

Grâce à @Izhaki commentaire, ici, c'est la référence à l' ngRepeat code source: https://github.com/angular/angular.js/blob/master/src/ng/directive/ngRepeat.js

10voto

mrvdot Points 41

Vous pouvez réellement gérer tout cela avec une simple balise de modèle. Voir http://jsfiddle.net/m4ve9/ pour un exemple. Notez qu'en fait je n'ai pas besoin d'une compilation ou d'un lien de propriété sur le super-définition de la directive.

Pendant le processus de compilation, Angulaire tire dans les valeurs de modèle avant de compiler, de sorte que vous pouvez joindre tout autres directives là et Angulaire prendra soin de cela pour vous.

Si c'est un super directive qui doit conserver l'original de contenu interne, vous pouvez utiliser transclude : true et remplacer l'intérieur avec <ng-transclude></ng-transclude>

Hope qui aide, laissez-moi savoir si quelque chose n'est pas clair

Alex

6voto

GFoley83 Points 1423

Voici une solution qui déplace les directives qui doivent être ajoutés de façon dynamique, dans le point de vue et ajoute également une option (de base), à la condition logique. Ce qui maintient la directive propre, sans codée en dur de la logique.

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 à penser à un cas d'utilisation pour une directive comme cela jusqu'à ce que j'ai pensé qu'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 option if de la propriété qui doit contenir une valeur booléenne, de l'expression ou de la fonction (par exemple défini dans votre controller) qui détermine si la directive doit être ajouté ou non.

Je suis également en utilisant attrs.$attr.dynamicDirectives d'obtenir la déclaration d'attribut utilisé pour ajouter de la directive (par exemple, data-dynamic-directive, dynamic-directive) sans coder en dur les valeurs de chaîne à vérifier.

Plunker Démo

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: 1000, // Run before other directives,
                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="http://stackoverflow.com//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>

1voto

Kemal Dağ Points 1503

Essayez de stocker l'état dans un attribut sur l'élément lui-même, tel que 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 ceci vous aide.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X