Тонкости AngularJS: select внутри шаблона директивы

Эта статья будет описывать решение одной конкретной задачи, а также на примере показывать как работает $transclude.Задача такая: сделать директиву, обертку для select-а. Предположим, что мы хотим одним тегом добавлять сразу и селект и label к нему (потом можно будет туда добавить ошибки заполнения, но мы для простоты не будем этого делать). В общем то, на первый взгляд выглядит все просто.

Сделаем директиву и назовем ее field. Использовать будем так:

Мы придумали несколько атрибутов: title — Название поля type — Тип поля (у нас будет только select, но вдруг потом…) ng-model — Переменная, которая хранит выбранное значение options — Чтобы задать список для селекта мы будем пользоваться синтаксисом англяровского ngOptions Обратите внимание, что переменные selectedColor и colors из примера, это переменные scope в котором мы использовали директиву. Мы специально их указали через атрибуты, чтобы директива получила к ним доступ.

Код директивы:

angular.module ('directives').directive ('field', function () { return { //директива — это название тега restrict: 'E',

// то, что мы передали в директиву scope: { ngModel:»=», title:»@», type:»@», options:»@» },

// адрес шаблончика templateUrl: '/tpl/fields/select.html', }); Код шаблона:

Вживую: codepen.io/Dzorogh/pen/umCKG? editors=101

Вроде, все выглядит просто и должно работать. Запускаем, проверяем в отладчике —

Вроде неплохо… только где все options? Почему colors — пуст (точнее, undefined)? Дело в том, что когда мы указываем в директиве параметр scope, то все тело директивы оборачивается каменной стеной — isolate scope. Это изолированый скоуп не дает тем, кто внутри, видеть внешние scope. Но, соответственно, все те переменные (ngModel, type, title, options) что мы указали параметре директивы, будут добавлены в наш изолированый scope и «привязаны» к внешним переменным.

И тут мы можем увидеть проблему — мы не добавили 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

© Habrahabr.ru