Diretiva AngularJS com opções padrão

145

Estou apenas começando com angularjs e estou trabalhando para converter alguns plugins JQuery antigos em diretivas Angular. Eu gostaria de definir um conjunto de opções padrão para minha diretiva (elemento), que pode ser substituída especificando o valor da opção em um atributo.

Examinei a maneira como os outros fizeram isso e, na biblioteca angular-ui , a página ui.bootstrap.pag parece fazer algo semelhante.

Primeiro, todas as opções padrão são definidas em um objeto constante:

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

Em seguida, uma getAttributeValuefunção de utilitário é anexada ao controlador de diretiva:

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

Por fim, isso é usado na função de vinculação para ler atributos como

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

Parece uma configuração bastante complicada para algo tão padrão quanto querer substituir um conjunto de valores padrão. Existem outras maneiras comuns de fazer isso? Ou é normal sempre definir uma função de utilidade como getAttributeValuee analisar opções dessa maneira? Estou interessado em descobrir quais estratégias diferentes as pessoas têm para essa tarefa comum.

Além disso, como bônus, não estou claro por que o interpolateparâmetro é obrigatório.

Ken Chatfield
fonte

Respostas:

108

Você pode usar compileatributos de leitura de função se eles não estiverem definidos - preencha-os com valores padrão.

.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_
fonte
1
Obrigado! Então, alguma idéia de por ui.bootstrap.paginationque as coisas são mais complicadas? Estava pensando que, se usando a função de compilação, qualquer alteração de atributo feita posteriormente não seria refletida, mas isso não parece ser verdade, pois apenas os padrões são definidos nesse estágio. Acho que deve haver alguma compensação sendo feita aqui.
Ken Chatfield 13/09
3
O @KenChatfield compilenão pode ler atributos, que devem ser interpolados para obter valor (que contém expressão). Mas se você quiser verificar apenas se o atributo está vazio - ele funcionará sem nenhuma troca por você (antes que o atributo de interpolação contenha sequência com expressão).
OZ_
1
Fantástico! Muito obrigado pela sua explicação clara. Para os leitores futuros, embora tangencial à pergunta original, para uma explicação do que o parâmetro 'interpolate' faz no ui.bootstrap.paginationexemplo que eu encontrei este exemplo muito útil: jsfiddle.net/EGfgH
Ken Chatfield
Muito obrigado por essa solução. Observe que, se você precisar da linkopção, ainda poderá retornar uma função em sua compileopção. doc aqui
mneute
4
Lembre-se de que os atributos precisam dos valores, pois seriam passados ​​do modelo. Se você está passando um array fe que deveria ser attributes.foo = '["one", "two", "three"]', em vez deattributes.foo = ["one", "two", "three"]
Dominik Ehrenberg
263

Use o =?sinalizador para a propriedade no bloco de escopo da diretiva.

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';
      }
    }
  });
Caçador
fonte
4
=?está disponível desde 1.1.x
Michael Radionov
34
Se seu atributo pudesse aceitar trueou falsecomo valores, você (eu acho) gostaria de usar, por exemplo $scope.hasName = angular.isDefined($scope.hasName) ? $scope.hasName : false;.
Paul D. Waite
22
Nota: ele funciona apenas com ligação bidirecional, por exemplo =?, mas não com ligação unidirecional @?.
9605 Justus Romijn
20
Isso também pode ser feito apenas no modelo: template: 'hello {{name || \ 'nome padrão \'}} '
Vil
4
O valor padrão deve ser definido no controlador ou na linkfunção? Com base no meu entendimento, atribuir durante o linkdeve evitar um $scope.$apply()ciclo, não deveria?
Augustin Riedinger 30/03
1

Estou usando o AngularJS v1.5.10 e achei a preLinkfunção de compilação funcionar muito bem para definir valores de atributo padrão.

Apenas um lembrete:

  • attrsmantém os valores brutos do atributo DOM que são sempre uma undefinedou uma sequência de caracteres.
  • scopemantém (entre outras coisas) os valores do atributo DOM analisados ​​de acordo com a especificação de escopo isolado fornecida ( =/ </ @/ etc.).

Snippet resumido:

.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
fonte