Directiva AngularJS con opciones predeterminadas

145

Estoy comenzando con angularjs, y estoy trabajando en convertir algunos viejos complementos de JQuery a directivas angulares. Me gustaría definir un conjunto de opciones predeterminadas para mi directiva (elemento), que se puede anular especificando el valor de la opción en un atributo.

He echado un vistazo a la forma en que otros lo han hecho, y en la biblioteca angular-ui , la ui.bootstrap.pagination parece hacer algo similar.

Primero, todas las opciones predeterminadas se definen en un objeto constante:

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

Luego getAttributeValuese adjunta una función de utilidad al controlador de la directiva:

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

Finalmente, esto se usa en la función de enlace para leer 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);
        ...
    }
});

Esto parece una configuración bastante complicada para algo tan estándar como querer reemplazar un conjunto de valores predeterminados. ¿Hay otras formas de hacer esto que sean comunes? ¿O es normal definir siempre una función de utilidad como getAttributeValuey analizar opciones de esta manera? Me interesa saber qué estrategias diferentes tienen las personas para esta tarea común.

Además, como beneficio adicional, no estoy claro por qué interpolatese requiere el parámetro.

Ken Chatfield
fuente

Respuestas:

108

Puede usar la compilefunción - leer atributos si no están configurados - llenarlos con valores predeterminados.

.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; }
    },
        ...
  }
});
ONZ_
fuente
1
¡Gracias! Entonces, ¿alguna idea de por qué las ui.bootstrap.paginationcosas son más complicadas? Estaba pensando que si se usa la función de compilación, cualquier cambio de atributo realizado más tarde no se reflejaría, pero esto no parece ser cierto ya que solo los valores predeterminados se establecen en esta etapa. Supongo que debe haber algún compromiso aquí.
Ken Chatfield
3
@KenChatfield en el compileque no puede leer los atributos, que deben interpolarse para obtener valor (que contiene expresión). Pero si desea verificar solo si el atributo está vacío, funcionará sin compensaciones para usted (antes de que el atributo de interpolación contenga una cadena con expresión).
OZ_
1
¡Fantástico! Muchas gracias por tu clara explicación. Para futuros lectores, aunque tangenciales a la pregunta original, para una explicación de lo que hace el parámetro 'interpolar' en el ui.bootstrap.paginationejemplo, encontré este ejemplo muy útil: jsfiddle.net/EGfgH
Ken Chatfield
Muchas gracias por esa solución. Tenga en cuenta que si necesita la linkopción, aún puede devolver una función en su compileopción. doc here
mneute
44
Recuerde que los atributos necesitan los valores, ya que se pasarían de la plantilla. Si está pasando una matriz de fe debería ser en attributes.foo = '["one", "two", "three"]'lugar deattributes.foo = ["one", "two", "three"]
Dominik Ehrenberg
263

Use la =?bandera para la propiedad en el bloque de alcance de la directiva.

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';
      }
    }
  });
Cazador
fuente
44
=?está disponible desde 1.1.x
Michael Radionov
34
Si su atributo pudiera aceptar trueo falsecomo valores, (creo) querría usar, por ejemplo, en su $scope.hasName = angular.isDefined($scope.hasName) ? $scope.hasName : false;lugar.
Paul D. Waite
22
Nota: sólo funciona con la unión de dos vías, por ejemplo =?, pero no con la unión de una sola vía, @?.
Justus Romijn
20
también se puede hacer solo en la plantilla: template: 'hello {{name || \ 'nombre predeterminado \'}} '
Vil
44
¿Debería establecerse el valor predeterminado en el controlador o en la linkfunción? Según mi comprensión, la asignación durante el linkdebe evitar un $scope.$apply()ciclo, ¿no?
Augustin Riedinger
1

Estoy usando AngularJS v1.5.10 y encontré que la preLinkfunción de compilación funciona bastante bien para establecer valores de atributo predeterminados.

Simplemente un recordatorio:

  • attrscontiene los valores de atributo DOM sin procesar que siempre son undefinedcadenas o.
  • scopecontiene (entre otras cosas) los valores del atributo DOM analizados de acuerdo con la especificación de alcance de aislamiento proporcionada ( =/ </ @/ etc.).

Fragmento abreviado:

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