Тонкости AngularJS: select внутри шаблона директивы
Эта статья будет описывать решение одной конкретной задачи, а также на примере показывать как работает $transclude.Задача такая: сделать директиву, обертку для select-а. Предположим, что мы хотим одним тегом добавлять сразу и селект и label к нему (потом можно будет туда добавить ошибки заполнения, но мы для простоты не будем этого делать). В общем то, на первый взгляд выглядит все просто.
Сделаем директиву и назовем ее field. Использовать будем так:
Код директивы:
angular.module ('directives').directive ('field', function () { return { //директива — это название тега restrict: 'E',
// то, что мы передали в директиву scope: { ngModel:»=», title:»@», type:»@», options:»@» },
// адрес шаблончика templateUrl: '/tpl/fields/select.html', }); Код шаблона:
Вроде, все выглядит просто и должно работать. Запускаем, проверяем в отладчике —
И тут мы можем увидеть проблему — мы не добавили colors к тем переменным. И это правильно, директива, разумеется, не должна знать что там за массив мы хотим использовать. Мы уже указали, что используем этот массив, написав его в атрибуте options.
Чтобы получить доступ к массиву, мы должны хотя бы знать название переменной. Поэтому самый лучший вариант — парсить options. Нам тут помогает исходный код angular.js, там есть регулярка для ngOptions.
Но подождите, даже получив название переменной из внешенго scope, как мы сможем ее добавить внутрь?
Собственно, самое интересное Чтобы связывать внешние переменный с внутренними после иницализации директивы нужно использовать такую штуку, как »$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]; }); } } } }); Конечный вариант в CodePen: codepen.io/Dzorogh/pen/gBbyI