И снова привет!
Сегодня мы поговорим о такой опции директив, как require. Понимание данного функционала AngularJS не входит в базовый уровень знаний, который требуется для разработки простых приложений, поэтому у многих вызывает кучу вопросов.
Очень часто при написании директив возникает необходимость в их взаимодействии, общении, чтоб одна из них могла, например, вызвать метод добавления нового элемента в массиве у другой. В AngularJS есть для этого готовый механизм, который мы сейчас и рассмотрим.
Ну что же, приступим к написанию нашего приложения, которое прояснит нам смысл этой загадочной опции require:
Разметка:
<developer>developer</developer>
Код приложения:
angular.module("developerApp", [])
.directive("developer", function() {
return {
restrict: "E",
controller: function($scope) {
$scope.skills = [];
this.iKnowJS = function() {
$scope.skills.push("JS");
};
this.iKnowHTML = function() {
$scope.skills.push("HTML");
};
this.iKnowAngularJS = function() {
$scope.skills.push("AngularJS");
};
},
link: function(scope, element){
element.bind("mouseenter", function() {
console.log(scope.skills);
});
}
}
});
В этом коде мы видим директиву developer со своим контроллером, который будет использоваться в качестве API (Application Programming Interface) при взаимодействии с другими директивами. Суть директивы developer проста — при наведении на элемент в консоль выводится список навыков разработчика.
Далее, мы добавим следующий код, который создаёт ещё одну директиву внутри приложения для взаимодействия с директивой developer.
.directive("js", function() {
return {
require: "developer",
link: function (scope, element, attrs, developerCtrl) {
developerCtrl.iKnowJS();
}
};
});
Как видно из кода, у директивы есть свойство require, которое делает инъекцию контроллера, расположенного в другой директиве developer, в link-функцию создаваемой путём добавления параметра функции (здесь это developerCtrl).
Надо заметить, что у этого параметра, запрашивающего контроллер, нет требований и соглашений по именованию.
Создадим ещё две директивы — «html» и «angularjs»:
.directive("html", function() {
return {
require: "developer",
link: function (scope, element, attrs, developerCtrl) {
developerCtrl.iKnowHTML();
}
};
}).directive("angularjs", function() {
return {
require: "developer",
link: function (scope, element, attrs, developerCtrl) {
developerCtrl.iKnowAngularJS();
}
};
});
Теперь каждая из этих директив связана с внешними API-методами контроллера директивы developer.
Мы можем изменить нашу разметку, добавив новые навыки нашему разработчику:
<developer js html angularjs>Senior developer</developer>
Как и предполагалось, отдельные директивы взаимодействуют с контроллером директивы элемента.
Изолированная область видимости
Но не всё так хорошо, как нам кажется! Давайте создадим несколько экземпляров директивы developer:
<developer js html angularjs>Senior developer</developer>
<developer html>Junior developer</developer>
Несмотря на то, что у каждой из них свой набор дополняющих директив, каждый экземпляр обрабатывается последовательно. И поскольку область видимости у этих двух элементов с директивой developer общая, и инициализация каждой из них перезаписывает значение предыдущей, мы получаем следствие — на какой бы элемент мы не навели курсор, получим значения от последней директивы.
Решением данной проблемы будет создание изолированной области видимости для директивы developer. Давайте так и сделаем:
angular.module("developerApp", [])
.directive("developer", function() {
return {
restrict: "E",
scope: {},
controller: function($scope) {
$scope.skills = [];
...
};
});
Вы можете посмотреть на полный код нашего приложения:
Общение с вышерасположенной директивой
В примерах выше происходило общение директив одного DOM-элемента (в нашем случае элемент developer). Но в некоторых случаях есть необходимость во взаимодействии между директивами на разных уровнях. Например, нижележащий элемент что-то делает с вышестоящим. На этот случай также есть готовое решение внутри фрэймворка. Для этого достаточно добавить в значение опции require префикс «^» («циркумфлекс» или «крышечка» в простонародье). Этот префикс означает, что директива будет искать контроллер на своем элементе или на элементах, расположенных выше.
Посмотрите на изменение кода в нашем приложении. Тут показано на примере общение вложенной директивы и расположенной на том же элементе:
Маленькое дополнение
Теперь, когда мы знаем, для чего нужна опция require, будет приятно узнать, что она может принимать в качестве значения несколько параметров, т.е. общение может происходить с несколькими директивами одновременно. В таком случае мы получим в функцию link вызывающей директивы не контроллер, а массив контроллеров. Пример:
require: ["developer", "^anyParentDirective"],
...
link: function(scope, element, attrs, controllers)
И ещё, хотелось бы с тобой поделиться советом из документации AngularJS по написанию директив. Кто-то из вас может спросить, в чём разница опции controller и link у директив? Основное различие в том, что контроллер может выставлять некий API наружу. Отсюда вывод, который относится к категории «Best Practices»: используйте опцию controller, если вы хотите дать API для других директив. В других случаях используйте функцию link.
Заключение
Ну что же? Я очень надеюсь, что эта статья тебе понравилась, и ты стал лучше понимать междирективное взаимодействие в AngularJS. Используя этот метод можно решить многие нетривиальные задачи, возникающие при разработке клиентского приложения. Не забудь поделиться ссылкой со всеми интересующимися!
До новых статей!