Dyrektywa AngularJS z domyślnymi opcjami

145

Właśnie zaczynam od angularjs i pracuję nad konwersją kilku starych wtyczek JQuery do dyrektyw Angular. Chciałbym zdefiniować zestaw domyślnych opcji dla dyrektywy my (element), które można przesłonić, podając wartość opcji w atrybucie.

Rozejrzałem się po tym, jak zrobili to inni, iw bibliotece angular -ui plik ui.bootstrap.pagination wydaje się robić coś podobnego.

Najpierw wszystkie domyślne opcje są zdefiniowane w stałym obiekcie:

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

Następnie getAttributeValuedo kontrolera dyrektywy dołączana jest funkcja użyteczności:

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

Na koniec jest to używane w funkcji łączenia do wczytywania atrybutów jako

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

Wydaje się, że jest to dość skomplikowana konfiguracja w przypadku czegoś tak standardowego, jak chęć zastąpienia zestawu wartości domyślnych. Czy istnieją inne popularne sposoby na zrobienie tego? A może to normalne, że zawsze definiuje się funkcję narzędzia, taką jak getAttributeValuei analizuje opcje w ten sposób? Jestem zainteresowany, aby dowiedzieć się, jakie różne strategie mają ludzie dla tego wspólnego zadania.

Dodatkowo, jako bonus, nie rozumiem, dlaczego ten interpolateparametr jest wymagany.

Ken Chatfield
źródło

Odpowiedzi:

108

Możesz użyć compileatrybutów function - read, jeśli nie są ustawione - wypełnij je wartościami domyślnymi.

.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_
źródło
1
Dzięki! A więc jakieś przemyślenia na temat tego, dlaczego ui.bootstrap.paginationsprawy są bardziej skomplikowane? Pomyślałem, że jeśli użyjesz funkcji kompilacji, wszelkie zmiany atrybutów dokonane później nie zostaną odzwierciedlone, ale to nie wydaje się być prawdą, ponieważ na tym etapie są ustawione tylko wartości domyślne. Chyba musi nastąpić jakiś kompromis.
Ken Chatfield,
3
@KenChatfield w compilenie można odczytać atrybutów, które powinny być interpolowane, aby uzyskać wartość (która zawiera wyrażenie). Ale jeśli chcesz sprawdzić tylko czy atrybut jest pusty - będzie działał bez żadnych kompromisów (przed interpolacją atrybut będzie zawierał łańcuch z wyrażeniem).
OZ_
1
Fantastyczny! Bardzo dziękuję za jasne wyjaśnienie. Dla przyszłych czytelników, choć stycznych do pierwotnego pytania, dla wyjaśnienia, co robi parametr „interpolate” w ui.bootstrap.paginationprzykładzie, znalazłem ten bardzo przydatny przykład: jsfiddle.net/EGfgH
Ken Chatfield
Wielkie dzięki za to rozwiązanie. Zwróć uwagę, że jeśli potrzebujesz tej linkopcji, nadal możesz zwrócić funkcję w swojej compileopcji. doc tutaj
mneute
4
Pamiętaj, że atrybuty wymagają wartości, ponieważ zostałyby przekazane z szablonu. Jeśli przekazujesz tablicę, na przykład powinna być attributes.foo = '["one", "two", "three"]'zamiastattributes.foo = ["one", "two", "three"]
Dominik Ehrenberg
263

Użyj =?flagi dla właściwości w bloku zakresu dyrektywy.

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';
      }
    }
  });
myśliwy
źródło
4
=?jest dostępny od 1.1.x
Michael Radionov
34
Gdyby twój atrybut mógł akceptować truelub falsejako wartości, chciałbyś (myślę) użyć np $scope.hasName = angular.isDefined($scope.hasName) ? $scope.hasName : false;. Zamiast tego.
Paul D. Waite
22
Uwaga: to działa tylko z wiązania dwukierunkowe, na przykład =?, ale nie z obowiązującymi jednokierunkowe @?.
Justus Romijn,
20
również można to zrobić tylko w szablonie: template: 'hello {{name || \ 'default name \'}} '
Vil
4
Czy należy ustawić wartość domyślną w sterowniku czy w linkfunkcji? W oparciu o moje zrozumienie, przypisywanie w trakcie linkpowinno unikać $scope.$apply()cyklu, prawda?
Augustin Riedinger
1

Używam AngularJS v1.5.10 i stwierdziłem, że preLinkfunkcja kompilacji działa raczej dobrze przy ustawianiu domyślnych wartości atrybutów.

Tylko przypomnienie:

  • attrsprzechowuje nieprzetworzone wartości atrybutów DOM, które są zawsze albo undefinedciągami albo ciągami.
  • scopeprzechowuje (między innymi) wartości atrybutów DOM przeanalizowane zgodnie z podaną specyfikacją zakresu izolowania ( =/ </ @/ itp.).

Skrócony fragment:

.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
źródło