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.
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';
}
}
});
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; }
},
...
}
});
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.
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).
ui.bootstrap.pagination
example I found this very useful example: jsfiddle.net/EGfgH
link
option, you can still return a function in your compile
option. doc here
attributes.foo = '["one", "two", "three"]'
instead of attributes.foo = ["one", "two", "three"]
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: ...
}
});
Success story sharing
=?
is available since 1.1.xtrue
orfalse
as values, you would (I think) want to use e.g.$scope.hasName = angular.isDefined($scope.hasName) ? $scope.hasName : false;
instead.=?
, but not with one-way binding,@?
.link
function? Based on my understanding, assigning during thelink
should avoid a$scope.$apply()
cycle, shouldn't it?