Пользователь
0,0
рейтинг
3 августа 2014 в 17:32

Разработка → Контроллеры директив в AngularJS tutorial

Немного о контроллерах директив. Зачем они нужны и чем отличаются от функции 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.
                         //Помните об этом каждый раз, когда выносите код директивы в контроллер.
                         //Вполне возможно, что потом захотите использовать в директиве методы чужого контроллера,
                         //который затрет ссылку на ваш контроллер.
                         //P.S. Если прописать директиву в свой же require, то контроллер можно получить (require: ['^parent', 'children'])
    }
  }
})

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 же содержится индивидуальная логика директивы, поэтому весь код не предназначенный для общего использования помещайте туда.
Олег Истомин @tamtakoe
карма
60,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (11)

  • +1
    > В-третьих, link всегда привязана к директиве, в то время как контроллер может быть общим. Думаю, в этом ключевое отличие.
    Это как?
    • +1
      Видимо имеется ввиду, что никто не может запретить вам переиспользовать контроллер в любом другом месте.
      • +1
        а что мешает функцию ликновки переиспользовать? )
        link_func = function(scope, element, attrs, ctrls) {
           console.log(ctrls); //[parentCtrl, childrenCtrl]. Тем не менее контроллер из children будет доступен в дочерней директиве
        }
        
        app.directive('baby', function() {
          return {
            restrict: 'E',
            link: link_func
          }
        })
        
        app.directive('baby2', function() {
          return {
            restrict: 'E',
            link: link_func
          }
        })
        
        
        • +1
          Ничто не мешает, конечно. Но контроллер можно переиспользовать через ng-controller хоть с as, хоть без него. И к директивам это может никакого отношения не иметь. А функция линковки вроде только для директив может пригодиться (ну, если её нормально использовать, конечно).
        • 0
          Функцию link или скорее весь объект определения директивы можно так же использовать с разными директивами. На этом построен метод расширения директив в angular-bootstap, например.

          Директивы tooltip и popover используют объект генерируемый одной и той же функцией $tooltip()

          .directive( 'tooltip', [ '$tooltip', function ( $tooltip ) {
            return $tooltip( 'tooltip', 'tooltip', 'mouseenter' );
          }])
          
          .directive( 'popover', [ '$tooltip', function ( $tooltip ) {
            return $tooltip( 'popover', 'popover', 'click' );
          }]);
          


          Но тут дело в том, что области видимости у функций link будет разная, в случае же с контроллером в каждую директиву передается один и тот же объект контроллера в котором можно хранить общие данные, да и по ресурсам памяти это экономичнее.
    • 0
      в статье примеры чересчур замороченные. вообще, когда надо организовать взаимодействие нескольких директив, связанных общей логикой, рекомендуют выносить эту логику в общий для них контроллер. ведь основная задача директив — создавать мостики между приложением и DOM — которая решается в функциях link. большего от них не требуется. думаю, что это имелось ввиду.
  • +1
    «Поэтому в $scope, например, можно записать настройки для дочерних директив.»

    Если уж у вас дочерние \ родительские директивы с контроллерами, то можно и вообще без $scope обойтись — читать настройки прямо из родительского контроллера. Это ещё один плюс контроллеров директивы — можно достичь гораздо более чистого и удобного MVVM используя в качестве контроллеров JS класс без привязки к $scope, вешая все публичные члены на 'this' (а остюда и использовать сторонние JS классы (не знающие про Ангулар, но использующие this) в виде контроллера).

    Вот пример plnkr.co/edit/2TWXGZzEaPMujPICTKMQ?p=preview. В нем класс CountController это JS класс, который вообще не привязан к библиотекам — использовать можно хоть в браузере, хоть с NodeJS (большой плюс для code reuse). Кроме того, внешний и внутренний интерфейс получается очень чистый — что внутри директивы, что снаружи работаем с одни и тем же JS объектом.

    • 0
      Эти подходы можно совмещать, если нужно дерево вложенных директив, взаимодействующих друг с другом.
  • +1
    Вообще, ng-controller — это такой синтаксический сахар, а в целом, суть ангуляра — это директивы. Самое важное и основополагающее в ангуляре — это $compile. Просто если сразу с этого начинать, ничего не поймешь :)
  • +2
    Вполне возможно, что потом захотите использовать в директиве методы чужого контроллера, который затрет ссылку на ваш контроллер.

    Ну, или не затрёт.

    app.directive('children', function() {
      return {
        controller: 'childrenCtrl',
        require: ['^parent', 'children'],
        link: function(scope, element, attrs, ctrls) {
          var parentCtrl = ctrls[0];
          var childrenCtrl = ctrls[1];
          // ...
        }
      }
    })
    
    • 0
      Хороший пример! Не догадался проверить

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.