AngularJS: Должен ли я преобразовать функцию связывания директивы в контроллер?

Я слышал, что рекомендуется использовать синтаксис controllerAs вместе с bindToController: true в директивах, использующих изолированную область. Ссылки: один, два

Предположим, у меня есть такая директива:

angular.module('MyModule').directive('MyDirective', function(User) {
  return {
    scope: {
      name: '='
    },
    templateUrl: 'my-template.html',
    link: function(scope) {
      scope.User = User;
      scope.doSomething = function() {
        // Do something cool
      };
    }
  };
});
<!-- my-template.html -->
<div>
  User Id: {{ User.id }}
  Name: {{ name }}
  <button ng-click="doSomething()">Do it</button>
</div>

Как видите, в этой директиве нет контроллера. Но чтобы использовать controllerAs и bindToController: true, мне нужен контроллер.

Является ли наилучшей практикой преобразование функции связывания в контроллер?

angular.module('MyModule').directive('MyDirective', function(User) {
  return {
    scope: {
      name: '='
    },
    templateUrl: 'my-template.html',
    bindToController: true,
    controllerAs: 'myCtrl',
    controller: function() {
      this.User = User;
      this.doSomething = function() {
        // Do something cool
      };
    }
  };
});
<!-- my-template.html -->
<div>
  User Id: {{ myCtrl.User.id }}
  Name: {{ myCtrl.name }}
  <button ng-click="myCtrl.doSomething()">Do it</button>
</div>

Насколько я понимаю, контроллер директивы следует использовать в качестве механизма для предоставления API директивы для связи между директивами.

Может ли кто-нибудь пролить свет на то, что лучше всего использовать в наши дни, имея в виду Angular 2.0?


person Misha Moroshko    schedule 05.01.2015    source источник
comment
Поправьте меня, если я ошибаюсь, но идея самой директивы заключалась не в том, чтобы привязывать реальные элементы HTML к некоторым поведениям, чтобы изменять элемент при каждом изменении данных (отражать изменения обратно конечным пользователям) или обновлять данные, когда пользователи взаимодействуют с элементом (посредством событий DOM)? Если это так, вопрос здесь будет заключаться в том, как отделить логику от директив (пусть контроллеры обрабатывают это) и позволить директивам обрабатывать только изменения графического интерфейса. Ты так думаешь?   -  person Mr. Duc Nguyen    schedule 13.01.2015


Ответы (5)


Я считаю лучшей практикой перемещать код инициализации и/или раскрывать функции API внутри контроллера директивы, потому что это служит двум целям:

1. Intialization of $scope 
2. Exposing an API for communication between directives

Инициализация области

Предположим, ваша директива определяет дочернюю область (или наследует область). Если вы инициализируете область внутри своей функции ссылки, то дочерние области не смогут получить доступ к каким-либо переменным области, определенным здесь, через наследование области. Это связано с тем, что функция родительской ссылки всегда выполняется после функции дочерней ссылки. По этой причине правильное место для инициализации области — внутри функции контроллера.

Предоставление API контроллера

Дочерние директивы могут получить доступ к контроллеру родительской директивы через свойство «требовать» в объекте определения директивы. Это позволяет директивам общаться. Чтобы это работало, родительский контроллер должен быть полностью определен, чтобы к нему можно было получить доступ из функции ссылки дочерней директивы. Лучше всего это реализовать в определении самой функции контроллера. Функции родительского контроллера всегда вызываются перед функциями дочернего контроллера.

Последние мысли

Важно понимать, что функция связи и функция контроллера служат двум совершенно разным целям. Функция контроллера была разработана для инициализации и директивной связи, а функция компоновщика была разработана для поведения во время выполнения. Основываясь на назначении вашего кода, вы сможете решить, принадлежит ли он контроллеру или компоновщику.

Следует ли переместить любой код, инициализирующий область действия, из функции связи в функцию контроллера?

Да, это одна из основных причин существования функции контроллера: инициализировать область видимости и позволить ей участвовать в прототипическом наследовании области.

Следует ли переместить обработчики $watch из функции ссылки в функцию контроллера?

Нет. Функция link предназначена для подключения поведения и потенциального манипулирования DOM. В функции ссылки все директивы скомпилированы, и все дочерние функции ссылки уже выполнены. Это делает его идеальным местом для подключения поведения, потому что он настолько близок к DOM-готовности, насколько это возможно (на самом деле он не готов к DOM до завершения фазы рендеринга).

person pixelbits    schedule 09.01.2015
comment
Я не понимаю, как наличие кода в двух разных местах, управляющего одной вещью, может облегчить нашу жизнь. - person artur grzesiak; 12.01.2015
comment
А как насчет: Лучшая практика: используйте контроллер, когда вы хотите предоставить доступ к API другим директивам. В противном случае используйте ссылку. Источник: docs.angularjs.org/guide/directive - person NoRyb; 17.04.2015

Я начну с вашей последней фразы. Все дело в том, как вы хотите написать свой угловой код. Если вы хотите придерживаться рекомендаций по написанию хорошего кода для angular 1.x, то даже не думайте слишком много о том, что является идеальным. Однако, если вы хотите подготовиться к следующей версии Angular, а также к грядущим веб-технологиям, я бы посоветовал вам начать применять новые концепции и адаптировать их к тому, как вы пишете свой код сегодня. Не забывайте, что в данном случае нет правильного или неправильного.

Говоря об angular 2.0 и ES6, я хотел бы подчеркнуть, что понятие директив будет больше соответствовать технологии веб-компонентов.

В Angular 2.0 (согласно текущему дизайну) избавится от сложного способа определения директив; Это больше не DDO. Поэтому я думаю, что было бы лучше, если бы вы начали думать таким образом. Компонент будет иметь только представление и контроллер.

Например,

@ComponentDirective({
    selector:'carousel',
    directives:[NgRepeat]
})
export class Carousel{  
    constructor(panes:Query<CarouselItem>) {
        this.items= panes;
    }

    select(selectedCarouselItem:CarouselItem) { ... }
}

Приведенный выше код написан на AtScript (надмножество машинописного текста и ES6), но вы сможете выразить то же самое и в ES5. Вы можете видеть, как все будет проще. В np есть такое понятие, как функция ссылки или компиляция и т. Д.

Кроме того, представление вышеуказанного компонента будет напрямую привязано к указанному выше классу; Таким образом, вы уже можете найти сходство с синтаксисом контроллера.

Итак, по сути, я бы посоветовал вам сначала взглянуть на общую идею веб-компонентов и на то, каким может быть будущее веб-разработок, а затем, я думаю, вы начали бы писать код Angular 1.x с учетом этого.

Подводя итог, попробуйте кодировать так, чтобы это благоприятствовало текущей версии Angular, но если вы считаете, что некоторые части вашего кода могут включать в себя некоторые концепции следующей версии, сделайте это. Я не верю, что это навредит тебе. Старайтесь не усложнять, так как новая версия Angular будет проще.

Я бы посоветовал вам прочитать следующие сообщения:

  1. https://www.airpair.com/angularjs/posts/component-based-angularjs-directives
  2. http://eisenbergeffect.bluespire.com/all-about-angular-2- 0/
  3. https://www.airpair.com/angularjs/posts/preparing-for-the-future-of-angularjs
  4. http://teropa.info/blog/2014/10/24/how-ive-improved-my-angular-apps-by-banning-ng-controller.html
person ppoliani    schedule 12.01.2015

ОБНОВИТЬ

(внизу я добавил код/plnkr, показывающий подход)

Помимо упомянутой вами статьи: https://www.airpair.com/angularjs/posts/preparing-for-the-future-of-angularjs#3-3-match-controllers-with-directives, который в основном не только защищает шаблон, который вы запрашиваете, но и внешний интерфейс на основе компонентов в целом, я нашел: http://joelhooks.com/blog/2014/02/11/lets-make-full-ass-angularjs-directives/ (он поддерживает Минимальное использование функции ссылки и используйте ui-bootstrap в качестве примера, где использовался такой шаблон). Я не могу не согласиться с обеими этими статьями.

Еще одна вещь об Angular2.0: больше нет $scope в angular2.0 -- https://www.youtube.com/watch?v=gNmWybAyBHI&t=12m14s, поэтому, если вы сможете максимально избавиться от $scope, то переход будет более плавным.

Я тоже сделал небольшую ошибку:

Тем не менее, я предпочитаю определять все функции в controller и просто вызывать их через область видимости link. В идеале это всего один вызов: scope.init ctrl.init(/*args*/) (где ctrl — контроллер директивы).


В какой-то степени это дело вкуса, но есть веские причины сделать функцию link как можно более тонкой:

  1. Логику в функции ссылки нелегко проверить. Конечно, вы можете скомпилировать директиву в своих модульных тестах и ​​протестировать ее поведение, но сама функция ссылки — это черный ящик.

  2. Если вам нужно использовать controller (скажем, для связи между директивами), то у вас будет два места для размещения вашего кода. Это сбивает с толку, но если вы решили сделать функцию link тонкой, то все, что можно поместить в controller, нужно поместить в controller.

  3. Вы не можете внедрить дополнительные зависимости непосредственно в функцию link (вы все равно можете использовать те, которые внедрены в функцию main). В случае подхода controller такой проблемы нет. Почему это важно:

    • it keeps better structure of the code, by having the dependencies closer to the context where they are needed
    • люди, приходящие в angular с фоном, отличным от JS, все еще имеют проблемы с тем, как функциональное закрытие работает в JS

Итак, что нужно поместить в функцию ссылки:

  1. Все, что нужно запустить после вставки элемента в DOM. Если $element выставлено $on('linked') событие, то в основном этот пункт недействителен.
  2. Получение ссылок на контроллеры require:ed. Опять же, если бы можно было ввести их напрямую в controller...

Тем не менее, я предпочитаю определять все функции в controller и просто вызывать их через область видимости link. В идеале это всего один вызов: scope.init.


Миско Хевери пару раз сказал, что DDO далека от совершенства и простоты для понимания, и что она эволюционировала до того, что есть сейчас. Я почти уверен, что если бы дизайнерские решения были приняты заранее, то было бы единственное место для размещения логики директивы - как это будет в angular2.0.


Теперь, отвечая на ваш вопрос, следует ли вам преобразовать функцию link в функцию controller. Это действительно зависит от ряда критериев, но если код активно развивается, то, наверное, стоит задуматься. Мой опыт (и несколько человек, о которых я говорил) можно проиллюстрировать этим изображением: The Link Mess Zenith

Что касается angular2.0 - это будет тектонический сдвиг, поэтому с этой точки зрения это не должно иметь большого значения, но все же подход controller кажется более близким к тому, как директивы/компоненты будут объявляться в версии 2.0 через Классы ES6.

И последнее: в какой-то степени это дело вкуса, но есть несколько веских причин, чтобы функция CONTROLLER оставалась тонкой (путем делегирования логики службам).


ОБНОВЛЕНИЕ -- PLNKR

PLNKR, иллюстрирующий подход:

html

<input ng-model="data.name"/>

<top-directive>
  <my-directive my-config="data">

  </my-directive>
</top-directive>

js

var app = angular.module('plunker', []);

app.controller('MainCtrl', function($scope) {
  $scope.data = { name : 'Hello, World'};
});

app.controller('MyCtrl', function($scope){

  var self = this;

  this.init = function(top){
    this.topCtrl = top;
    this.getTopName = top.getName.bind(top);
    this.getConfigName = function(){return this.config.name}; 
    console.log('initilizing', this, $scope, this.getConfigName, this.getTopName());
  }

  // if you want to $watch you have to inject $scope
  // you have access to the controller via name defined 
  // in contollerAs
  $scope.$watch('myCtrl.config', function(){
    console.log('config changed', self.getConfigName());
  }, true);
});

app.directive('topDirective', function(){

  return {
    controller : function(){
      this.name = "Hello, Top World";
      this.getName = function(){return this.name};
    }
  }

});

app.directive('myDirective', function(){

  return {

    require: ['myDirective', '^topDirective'],

    controller : 'MyCtrl',
    bindToController: true,
    controllerAs: 'myCtrl',

    template : '{{myCtrl.getConfigName() + " --- " + myCtrl.getTopName()}} ',

    scope : {
     config : "=myConfig",
    },

    link : function(scope, element, attrs,  Ctrls){
      Ctrls[0].init(Ctrls[1]);
    }
  }

});

person artur grzesiak    schedule 09.01.2015
comment
Спасибо, что просветили меня, что вы можете получить доступ к собственному контроллеру директивы внутри функции связывания, просто перетащив его в «требовать». - person Maciej Gurban; 11.05.2015

Согласно последней документации, по-прежнему рекомендуется использовать контроллер, когда вы хотите предоставить доступ к API. к другим директивам. В противном случае используйте ссылку." Я также хотел бы услышать от других людей и о подходе, который они используют.

person Aman Mahajan    schedule 06.01.2015

делюсь содержимым здесь (у меня недостаточно репутации, чтобы размещать это как комментарии)

“where do I put code, in ‘controller’ or ‘link’?”

  • Перед компиляцией? – Контроллер
  • После компиляции? - Связь

Couple of things to note:

  1. контроллер «$scope» и ссылка «scope» — это одно и то же. Разница в том, что параметры, отправляемые в контроллер, попадают туда через внедрение зависимостей (поэтому требуется вызов «$scope»), где параметры, отправляемые в ссылку, являются стандартными функциями, основанными на порядке. Во всех примерах angular будет использоваться «scope» в контексте, но я обычно называю его $scope по соображениям здравого смысла: http://plnkr.co/edit/lqcoJj?p=preview

  2. $scope/scope в этом примере просто передается от родительского контроллера.

  3. «ссылка» в директивах на самом деле является функцией «пост-ссылки» (см. конвейер рендеринга ниже). Поскольку предварительная ссылка используется редко, опция «ссылка» — это просто ярлык для настройки функции «пост-ссылки».

Итак, что такое реальный пример? Ну, когда я решаю, я исхожу из этого:

  • «Я просто делаю шаблоны и масштабы?» - входит в контроллер
  • «Добавлю ли я библиотеку jquery от coolbeans?» - идет по ссылке

Ответ за ответ принадлежит jasonmore

person algos    schedule 08.01.2015