http://habrahabr.ru/post/232023/
Немного о контроллерах директив. Зачем они нужны и чем отличаются от функции
link
.
Контроллер директивы vs контроллер вида
Объявление контроллера в директиве:
app.controller('myCtrl', function($scope, $element, $attrs) {
this.name = 'myCtrl'
)
app.directive('myDirective', function() {
return {
controller: 'myCtrl',
link: function(scope, element, attrs, ctrl) {
console.log(ctrl.name) //'myCtrl'
}
}
});
Вопрос на засыпку. Чем отличается контроллер директивы, от контроллера вида, который мы указываем через
ng-controller
(или другим способом)?
Да ничем. Это одно и то же. Если мы используем его в директиве, то в параметры
$scope, $element, $attrs
приходит область видимости и элемент директивы, если в шаблоне — приходит область видимости и параметры элемента, на котором висит
ng-controller
.
Отсюда вывод. Если вы наколбасили кучу похожей логики в контроллерах роутера или шаблонов с
ng-controller
, то достаточно просто создать директиву и перенести всю логику в ее контроллер, упростив тем самым код приложения.
Контроллер директивы vs link
Разбираемся дальше. В объекте определения директивы есть хорошо известная функция
link
, которая, на первый взгляд, ничем не отличается от контроллера. Но отличия все же есть.
Во-первых, она вызывается позже контроллера. Последовательность вызовов такая:
controller, preLink, postLink
(
postLink
это и есть
link
. Подробнее в
документации). Контроллер срабатывает даже раньше чем инициализируются директивы вложенных элементов. Поэтому в
$scope
, например, можно записать настройки для дочерних директив.
Во-вторых, в
link
передается ссылка на контроллер.
В-третьих,
link
всегда привязана к директиве, в то время как контроллер может быть общим. Думаю, в этом ключевое отличие.
Пример использования сторонних контроллеров в директиве, чтобы не отрываться от кода:
<parent>
<children>
<baby></baby>
</children>
</parent>
app.directive('parent', function() {
return {
controller: 'parentCtrl',
link: function(scope, element, attrs, ctrl) {
console.log(ctrl); //parentCtrl. Здесь будет собственный контроллер директивы
}
}
});
app.directive('children', function() {
return {
require: '^parent',
controller: 'childrenCtrl',
link: function(scope, element, attrs, ctrl) {
console.log(ctrl); //parentCtrl. А сюда уже попадет только контроллер родительской директивы, объявленой в require.
//Помните об этом каждый раз, когда выносите код директивы в контроллер.
//Вполне возможно, что потом захотите использовать в директиве методы чужого контроллера,
//который затрет ссылку на ваш контроллер.
}
}
})
app.directive('baby', function() {
return {
restrict: 'E',
require: ['^parent', '^children'],
link: function(scope, element, attrs, ctrls) {
console.log(ctrls); //[parentCtrl, childrenCtrl]. Тем не менее контроллер из children будет доступен в дочерней директиве
}
}
})
Живой пример в планкере
В
controller
мы создаем экземпляр контроллера, в
require
обращаемся к созданным экземплярам, что дает возможность хранить в контроллере общие данные.
Можно выделить два случая использования контроллеров: разделение общих методов между разными директивами, как работает, например,
ngModelController
в Ангуляре, и использование вложенными директивами данных родительской директивы, как работают табы, карусели и т.п., где есть родитель-контейнер и вложенные элементы. Если в первом случае контроллер работает как набор абстрактных методов, то во втором — может хранить в себе ссылку на список элементов, номер активного элемента, общее количество элементов и другую общую информацию. Конечно, для этой цели допустимо использование
$scope
, но, очевидно, оно будет не по назначению, т.к. область видимости нужна прежде всего для связывания контроллера с видом, а не для разделения общих данных.
В
link
же содержится индивидуальная логика директивы, поэтому весь код не предназначенный для общего использования помещайте туда.