Directive AngularJS avec options par défaut

145

Je commence juste avec angularjs et je travaille sur la conversion de quelques anciens plugins JQuery en directives Angular. Je voudrais définir un ensemble d'options par défaut pour ma directive (élément), qui peut être remplacée en spécifiant la valeur d'option dans un attribut.

J'ai jeté un coup d'œil à la manière dont d'autres ont fait cela, et dans la bibliothèque angular-ui , ui.bootstrap.pagination semble faire quelque chose de similaire.

Tout d'abord, toutes les options par défaut sont définies dans un objet constant:

.constant('paginationConfig', {
  itemsPerPage: 10,
  boundaryLinks: false,
  ...
})

Ensuite, une getAttributeValuefonction utilitaire est attachée au contrôleur de directive:

this.getAttributeValue = function(attribute, defaultValue, interpolate) {
    return (angular.isDefined(attribute) ?
            (interpolate ? $interpolate(attribute)($scope.$parent) :
                           $scope.$parent.$eval(attribute)) : defaultValue);
};

Enfin, cela est utilisé dans la fonction de liaison pour lire les attributs comme

.directive('pagination', ['$parse', 'paginationConfig', function($parse, config) {
    ...
    controller: 'PaginationController',
    link: function(scope, element, attrs, paginationCtrl) {
        var boundaryLinks = paginationCtrl.getAttributeValue(attrs.boundaryLinks,  config.boundaryLinks);
        var firstText = paginationCtrl.getAttributeValue(attrs.firstText, config.firstText, true);
        ...
    }
});

Cela semble être une configuration assez compliquée pour quelque chose d'aussi standard que de vouloir remplacer un ensemble de valeurs par défaut. Existe-t-il d'autres moyens de le faire qui sont courants? Ou est-il normal de toujours définir une fonction utilitaire telle que getAttributeValueet analyser les options de cette manière? Je suis intéressé de savoir quelles sont les différentes stratégies des gens pour cette tâche commune.

De plus, en prime, je ne sais pas pourquoi le interpolateparamètre est requis.

Ken Chatfield
la source

Réponses:

108

Vous pouvez utiliser la compilefonction - lire les attributs s'ils ne sont pas définis - les remplir avec des valeurs par défaut.

.directive('pagination', ['$parse', 'paginationConfig', function($parse, config) {
    ...
    controller: 'PaginationController',
    compile: function(element, attrs){
       if (!attrs.attrOne) { attrs.attrOne = 'default value'; }
       if (!attrs.attrTwo) { attrs.attrTwo = 42; }
    },
        ...
  }
});
OZ_
la source
1
Merci! Alors, des pensées sur pourquoi les ui.bootstrap.paginationchoses sont-elles plus compliquées? Je pensais que si vous utilisiez la fonction de compilation, les modifications d'attribut apportées ultérieurement ne seraient pas reflétées, mais cela ne semble pas être vrai car seules les valeurs par défaut sont définies à ce stade. Je suppose qu'il doit y avoir un compromis à faire ici.
Ken Chatfield
3
@KenChatfield dans compilevous ne pouvez pas lire les attributs, qui doivent être interpolés pour obtenir une valeur (qui contient une expression). Mais si vous voulez vérifier uniquement si l'attribut est vide - cela fonctionnera sans compromis pour vous (avant que l'attribut d'interpolation contienne une chaîne avec une expression).
OZ_
1
Fantastique! Merci beaucoup pour votre explication claire. Pour les futurs lecteurs, bien que tangentiel à la question originale, pour une explication de ce que fait le paramètre «interpoler» dans l' ui.bootstrap.paginationexemple, j'ai trouvé cet exemple très utile: jsfiddle.net/EGfgH
Ken Chatfield
Merci beaucoup pour cette solution. Notez que si vous avez besoin de l' linkoption, vous pouvez toujours renvoyer une fonction dans votre compileoption. doc here
mneute
4
N'oubliez pas que les attributs ont besoin des valeurs telles qu'elles seraient transmises à partir du modèle. Si vous passez un tableau fe il devrait être à la attributes.foo = '["one", "two", "three"]'place deattributes.foo = ["one", "two", "three"]
Dominik Ehrenberg
263

Utilisez l' =?indicateur de la propriété dans le bloc de portée de la directive.

angular.module('myApp',[])
  .directive('myDirective', function(){
    return {
      template: 'hello {{name}}',
      scope: {
        // use the =? to denote the property as optional
        name: '=?'
      },
      controller: function($scope){
        // check if it was defined.  If not - set a default
        $scope.name = angular.isDefined($scope.name) ? $scope.name : 'default name';
      }
    }
  });
chasseur
la source
4
=?est disponible depuis 1.1.x
Michael Radionov
34
Si votre attribut pouvait accepter trueou falsecomme valeurs, vous voudriez (je pense) utiliser par exemple à la $scope.hasName = angular.isDefined($scope.hasName) ? $scope.hasName : false;place.
Paul D.Waite
22
Remarque: il ne fonctionne qu'avec deux voies de liaison, par exemple =?, mais pas avec la liaison à sens unique , @?.
Justus Romijn
20
peut également être fait dans le modèle uniquement: modèle: 'bonjour {{nom || \ 'default name \'}} '
Vil
4
La valeur par défaut doit-elle être définie dans le contrôleur ou dans la linkfonction? D'après ce que j'ai compris, l'attribution pendant le linkdevrait éviter un $scope.$apply()cycle, n'est-ce pas?
Augustin Riedinger
1

J'utilise AngularJS v1.5.10 et j'ai trouvé que la preLinkfonction de compilation fonctionnait plutôt bien pour définir les valeurs d'attribut par défaut.

Juste un rappel:

  • attrscontient les valeurs d'attribut DOM brutes qui sont toujours des undefinedchaînes ou des chaînes.
  • scopecontient (entre autres) les valeurs d'attribut DOM analysées conformément à la spécification de portée d'isolat fournie ( =/ </ @/ etc.).

Extrait abrégé:

.directive('myCustomToggle', function () {
  return {
    restrict: 'E',
    replace: true,
    require: 'ngModel',
    transclude: true,
    scope: {
      ngModel: '=',
      ngModelOptions: '<?',
      ngTrueValue: '<?',
      ngFalseValue: '<?',
    },
    link: {
      pre: function preLink(scope, element, attrs, ctrl) {
        // defaults for optional attributes
        scope.ngTrueValue = attrs.ngTrueValue !== undefined
          ? scope.ngTrueValue
          : true;
        scope.ngFalseValue = attrs.ngFalseValue !== undefined
          ? scope.ngFalseValue
          : false;
        scope.ngModelOptions = attrs.ngModelOptions !== undefined
          ? scope.ngModelOptions
          : {};
      },
      post: function postLink(scope, element, attrs, ctrl) {
        ...
        function updateModel(disable) {
          // flip model value
          var newValue = disable
            ? scope.ngFalseValue
            : scope.ngTrueValue;
          // assign it to the view
          ctrl.$setViewValue(newValue);
          ctrl.$render();
        }
        ...
    },
    template: ...
  }
});
Keego
la source