ChatGPT解决这个技术问题 Extra ChatGPT

AngularJS directive with default options

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.


g
gae123

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

=? is available since 1.1.x
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.
Note: it only works with two-way binding, e.g. =?, but not with one-way binding, @?.
also can be done in template only: template: 'hello {{name || \'default name\'}}'
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?
A
Andrew Tobilko

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

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.
@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).
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
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
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"]
K
Keego

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