Subtleties of AngularJS: select inside the directive template
This article will describe the solution to one specific problem, as well as an example to show how $ transclude works.
The task is this: to make a directive, a wrapper for select. Suppose that we want to add both a select and a label to it with one tag at a time (then we can add filling errors there, but for simplicity we will not do this). In general, at first glance everything looks simple.
Let's make a directive and call it field. We will use it like this:
We came up with several attributes:
Note that the variables
Directive Code:
Template Code:
It seems that everything looks simple and should work. Run, check in the debugger -
It seems not bad ... but where are all the options? Why are colors empty (or rather, undefined)?
The fact is that when we specify the scope parameter in the directive, the entire body of the directive turns into a stone wall - isolate scope. This isolated scope does not allow those inside to see external scope. But, accordingly, all those variables (
And here we can see the problem - we did not add
To access the array, we must at least know the name of the variable. Therefore, the best option is to parse options. The source code of angular.js helps us here, there is a regularity for ngOptions there.
But wait, even after getting the variable name from the outer scope, how can we add it inside?
Actually, the most interesting
To bind external variables with internal ones after directive initialization, you need to use such a thing as "$ transclude". Calling this function gives access to an external scope and you can simply get any variable from there and “pass” directives to the scope.
New directive, with regular and transclude:
The task is this: to make a directive, a wrapper for select. Suppose that we want to add both a select and a label to it with one tag at a time (then we can add filling errors there, but for simplicity we will not do this). In general, at first glance everything looks simple.
Let's make a directive and call it field. We will use it like this:
We came up with several attributes:
- title - field name
- type - Field type (we will only have select, but suddenly then ...)
- ng-model - A variable that stores the selected value
- options - To specify a list for the select we will use the syntax of Anglar's ngOptions
Note that the variables
selectedColor
, and colors
in the example, the variables scope in which we used the directive. We specifically specified them through the attributes so that the directive gets access to them. Directive Code:
angular.module('directives').directive('field', function () {
return {
//директива - это название тега
restrict: 'E',
// то, что мы передали в директиву
scope: {
ngModel: "=",
title: "@",
type: "@",
options: "@"
},
// адрес шаблончика
templateUrl: '/tpl/fields/select.html',
});
Template Code:
Live: codepen.io/Dzorogh/pen/umCKG?editors=101
It seems that everything looks simple and should work. Run, check in the debugger -
It seems not bad ... but where are all the options? Why are colors empty (or rather, undefined)?
The fact is that when we specify the scope parameter in the directive, the entire body of the directive turns into a stone wall - isolate scope. This isolated scope does not allow those inside to see external scope. But, accordingly, all those variables (
ngModel, type, title, options
) that we specified for the directive parameter will be added to our isolated scope and “bound” to external variables. And here we can see the problem - we did not add
colors
to those variables. And rightly so, the directive, of course, should not know what kind of array we want to use there. We have already indicated that we use this array by writing it in the options attribute.To access the array, we must at least know the name of the variable. Therefore, the best option is to parse options. The source code of angular.js helps us here, there is a regularity for ngOptions there.
But wait, even after getting the variable name from the outer scope, how can we add it inside?
Actually, the most interesting
To bind external variables with internal ones after directive initialization, you need to use such a thing as "$ transclude". Calling this function gives access to an external scope and you can simply get any variable from there and “pass” directives to the scope.
New directive, with regular and transclude:
.directive('field', function() {
// позаимствовано из исходников angularjs
var NG_OPTIONS_REGEXP = /^\s*(.*?)(?:\s+as\s+(.*?))?(?:\s+group\s+by\s+(.*))?\s+for\s+(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+(.*?)(?:\s+track\s+by\s+(.*?))?$/;
};
return {
restrict: 'E',
scope: {
ngModel: "=",
type: "@",
title: "@",
options: "@"
},
// "Включаем" возможность использовать трансклюд
transclude: true,
templateUrl: '/fields/select.html',
// $transclude всегда идет 5м параметром.
link: function (scope, element, attrs, controller, $transclude) {
if (scope.type == 'select') {
// Парсинг названия массива значений
var parsedOptions = attrs.options.match(NG_OPTIONS_REGEXP);
// убираем фильтры (они пишутся после | )
var optionsArray = /^(.*) \|/.exec(parsedOptions[7]) || parsedOptions[7];
// optionsArray - название (только название) массива, в котором содержится список значений.
// чтобы получить к нему доступ, нужно взять его из общего Scope
// (того, где появилась директива) и добавить в наш.
$transclude(function (clone, $outerScope) {
scope[optionsArray] = $outerScope[optionsArray];
});
}
}
}
});
Final option in CodePen: codepen.io/Dzorogh/pen/gBbyI