Директива AngularJS с опциями по умолчанию

145

Я только начинаю с angularjs и работаю над преобразованием нескольких старых плагинов JQuery в директивы Angular. Я бы хотел определить набор параметров по умолчанию для моей директивы (element), который можно переопределить, указав значение параметра в атрибуте.

Я оглянулся на то, как это сделали другие, и в библиотеке angular-ui, похоже, ui.bootstrap.pagination делает нечто подобное.

Сначала все параметры по умолчанию определяются в постоянном объекте:

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

Затем getAttributeValueк контроллеру директивы присоединяется служебная функция:

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

Наконец, это используется в функции связывания для чтения в атрибутах как

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

Это выглядит довольно сложной настройкой для чего-то стандартного, например, для замены набора значений по умолчанию. Есть ли другие способы сделать это, которые распространены? Или это нормально - всегда определять такие служебные функции, как getAttributeValueи параметры синтаксического анализа? Мне интересно узнать, какие у людей разные стратегии для решения этой общей задачи.

Также в качестве бонуса мне не понятно, зачем interpolateнужен этот параметр.

Кен Чатфилд
источник

Ответы:

108

Вы можете использовать compileфункцию - читать атрибуты, если они не установлены - заполнить их значениями по умолчанию.

.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_
источник
1
Спасибо! Так что есть мысли о том, почему ui.bootstrap.paginationвсе сложнее? Думал, что если использовать функцию компиляции, любые изменения атрибутов, сделанные позже, не будут отражены, но это не похоже на правду, так как на этом этапе устанавливаются только значения по умолчанию. Думаю, здесь должен быть какой-то компромисс.
Кен Чатфилд
3
@KenChatfield в compileвы не можете прочитать атрибуты, которые должны быть интерполированы, чтобы получить значение (которое содержит выражение). Но если вы хотите проверить только, если атрибут пуст - он будет работать без каких-либо компромиссов для вас (перед атрибутом интерполяции будет содержаться строка с выражением).
OZ_
1
Фантастика! Большое спасибо за ваше четкое объяснение. Для будущих читателей, хотя они и касаются первоначального вопроса, для объяснения того, что делает параметр «интерполировать» в ui.bootstrap.paginationпримере, я нашел этот очень полезный пример: jsfiddle.net/EGfgH
Кен Чатфилд,
Большое спасибо за это решение. Обратите внимание, что если вам нужна linkопция, вы все равно можете вернуть функцию в ваш compileвариант. документ здесь
Mneute
4
Помните, что атрибутам нужны значения так, как они будут переданы из шаблона. Если вы передаете массив, то он должен быть attributes.foo = '["one", "two", "three"]'вместоattributes.foo = ["one", "two", "three"]
Доминик Эренберг
263

Используйте =?флаг для свойства в блоке области действия директивы.

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';
      }
    }
  });
охотник
источник
4
=?доступен с 1.1.x
Михаил Радионов
34
Если бы ваш атрибут мог принимать trueили в falseкачестве значений, вы бы (я думаю) захотели использовать, например, $scope.hasName = angular.isDefined($scope.hasName) ? $scope.hasName : false;вместо этого.
Пол Д. Уэйт
22
Примечание: он работает только с двусторонним связыванием, например =?, но не с односторонним связыванием @?.
Юстус Ромейн
20
также может быть сделано только в шаблоне: template: 'hello {{name || \ 'имя по умолчанию \'}}»
Виль
4
Должно ли значение по умолчанию быть установлено в контроллере или в linkфункции? Исходя из моего понимания, назначение во время linkдолжно избегать $scope.$apply()цикла, не так ли?
Августин Ридингер
1

Я использую AngularJS v1.5.10 и нашел preLink функция компиляции довольно хорошо работает для установки значений атрибутов по умолчанию.

Просто напоминание:

  • attrsдержит сырье значения атрибута DOM, которые всегда являются либо undefinedстроками, либо
  • scopeсодержит (среди прочего) значения атрибута DOM, проанализированные в соответствии с предоставленной спецификацией изолированной области ( =/< / @/ и т. д.).

Сокращенный фрагмент:

.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
источник