AngularJS directive with default options

87,290

Solution 1

You can use compile function - read attributes if they are not set - fill them with default values.

.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; }
    },
        ...
  }
});

Solution 2

Use the =? flag for the property in the scope block of the 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';
      }
    }
  });

Solution 3

I'm using AngularJS v1.5.10 and found the preLink compile function to work rather well for setting default attribute values.

Just a reminder:

  • attrs holds the raw DOM attribute values which are always either undefined or strings.
  • scope holds (among other things) the DOM attribute values parsed according to the provided isolate scope specification (= / < / @ / etc.).

Abridged snippet:

.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: ...
  }
});
Share:
87,290

Related videos on Youtube

Ken Chatfield
Author by

Ken Chatfield

Updated on June 19, 2020

Comments

  • Ken Chatfield
    Ken Chatfield about 4 years

    I'm just starting with angularjs, and am working on converting a few old JQuery plugins to Angular directives. I'd like to define a set of default options for my (element) directive, which can be overridden by specifying the option value in an attribute.

    I've had a look around for the way others have done this, and in the angular-ui library the ui.bootstrap.pagination seems to do something similar.

    First all default options are defined in a constant object:

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

    Then a getAttributeValue utility function is attached to the directive controller:

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

    Finally, this is used in the linking function to read in attributes as

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

    This seems like a rather complicated setup for something as standard as wanting to replace a set of default values. Are there any other ways to do this that are common? Or is it normal to always define a utility function such as getAttributeValue and parse options in this way? I'm interested to find out what different strategies people have for this common task.

    Also, as a bonus, I'm not clear why the interpolate parameter is required.

  • Ken Chatfield
    Ken Chatfield almost 11 years
    Thanks! So any thoughts on why ui.bootstrap.pagination does things in a more complicated way? Was thinking that if using the compile function any attribute changes made later would not be reflected, but this doesn't appear to be true as only the defaults are set at this stage. Guess there must be some tradeoff being made here.
  • OZ_
    OZ_ almost 11 years
    @KenChatfield in compile you can't read attributes, which should be interpolated to get value (which contains expression). But if you want to check only if attribute is empty - it will work without any tradeoffs for you (before interpolation attribute will contain string with expression).
  • Ken Chatfield
    Ken Chatfield almost 11 years
    Fantastic! Thanks very much for your clear explanation. For future readers, although tangential to the original question, for an explanation of what the 'interpolate' parameter does in the ui.bootstrap.pagination example I found this very useful example: jsfiddle.net/EGfgH
  • Michael Radionov
    Michael Radionov over 10 years
    =? is available since 1.1.x
  • Joe Dargie
    Joe Dargie over 10 years
    If your attribute could accept true or false as values, you would (I think) want to use e.g. $scope.hasName = angular.isDefined($scope.hasName) ? $scope.hasName : false; instead.
  • Justus Romijn
    Justus Romijn almost 10 years
    Wow, this is a really clean solution. Couldn't find such a good example for default value usage on the Angular Docs. +1!
  • Justus Romijn
    Justus Romijn almost 10 years
    Note: it only works with two-way binding, e.g. =?, but not with one-way binding, @?.
  • Fredrik Boström
    Fredrik Boström almost 10 years
    Is there a better way than doing scope: { someValueAttr: '@someValue' }, and in link function scope.someValue = someValueAttr || 0? Since you cannot write to a @ scope variable.
  • Vildan
    Vildan over 9 years
    also can be done in template only: template: 'hello {{name || \'default name\'}}'
  • mneute
    mneute over 9 years
    Thanks a lot for that solution. Note that if you need the link option, you can still return a function in your compile option. doc here
  • Augustin Riedinger
    Augustin Riedinger over 9 years
    Should the default value be set in the controller or in the link function? Based on my understanding, assigning during the link should avoid a $scope.$apply() cycle, shouldn't it?
  • Dominik Ehrenberg
    Dominik Ehrenberg about 9 years
    Remember, that attributes needs the values as they would be passed from the template. If you're passing an array f.e. it should be attributes.foo = '["one", "two", "three"]' instead of attributes.foo = ["one", "two", "three"]
  • Eugene Kulabuhov
    Eugene Kulabuhov almost 9 years
    &? defaults to undefined only starting with version 1.4.x. Before that you can use: $attrs.optionalProp === undefined
  • QOI
    QOI about 7 years
    But this will only work assuming that name doesn't change after the first initialization, if name is changed to undefined later, from outside the directive, the code to assign the default will not be triggered.
  • Ivan Prihhodko
    Ivan Prihhodko almost 7 years
    Better answer here as it adhere's to the Angular API, this should be the accepted answer.
  • PhiLho
    PhiLho almost 6 years
    Some notes: Without ? it is still optional, there is just a subtle difference, defined in docs.angularjs.org/api/ng/service/$compile#-scope- (always hard to find back...). Somehow, $scope.optional === undefined is true in both cases. About later change mentioned by @QOI, target has to be notified, can be done by watching the attribute or the scope value. ? now work on all kinds of binding.