Функции управления функциями углового управления

У меня есть директива для диаграммы:

.directive('chart', function() {
 return {
 ...
 controller: function($scope) {
 this.toggleAnimation = function() {
 ...
 };
 },
 link: function link(scope, element, attrs) {
 ...
 }
 }
});

И я использую его так:

<div ng-controller="foo">
 
</div>

Где foo:

.controller('foo', function($scope) {
 // TODO: call chart toggleAnimation
});

Теперь, как мне вызвать функцию toggleAnimation в директиве chart изнутри контроллера foo?

Или это не так, как должна быть установка? То, что я пытаюсь сделать здесь, - это создать функцию для моей директивы chart которая позволяет любому, потребляющему ее, превращать переменную в директиву в true/false.

3 ответа

Существует два основных механизма, с помощью которых данные могут протекать между конкретными директивами или контроллерами. Данные могут либо течь по иерархии областей (которая обычно отражает дерево DOM) с использованием областей и выражений, либо она может протекать по иерархии с помощью API-интерфейсов контроллера. Оба этих механизма влекут за собой одну директиву, связанную с другой конкретной директивой.

Третий механизм связи - это события сферы. Этот механизм связан с одной директивой, сообщающей с нулевыми или более другими директивами/контроллерами, о которых он не обязательно знает.

Какой механизм использовать зависит от конкретного сценария. В следующих разделах дается обзор каждого, за которым следует округление компромиссов каждого из них. (В конкретном примере, который вы дали, я бы использовал первый, но вы, похоже, заинтересованы в общих механизмах, а не только в вашем конкретном примере.)

Идиоматический способ передачи данных по дереву - предоставить доступ к диаграмме для данных из его родительской области. В этом случае он будет использоваться следующим образом:

<div ng-controller="Foo">
 
</div>

chartAnimated в вышеперечисленном является переменной области, вставленной контроллером. Вот как это выглядит в контроллере Foo:

.controller('Foo', function($scope) {
 $scope.chartAnimated = true;
 $scope.toggleAnimation = function () {
 $scope.chartAnimated = ! $scope.chartAnimated;
 };
});

Затем директиве диаграммы необходимо поддерживать этот новый атрибут, который может быть достигнут с использованием свойства scope в объявлении директивы:

.directive('chart', function() {
 return {
 scope: {
 // This requests that Angular parse the expression in the 'animated'
 // attribute and write a function for it into the scope as
 // 'animationEnabled'.
 'animationEnabled': '&animated'
 },
 link: function link(scope, iElement, attrs) {
 // Now we can watch the expression to detect when it changes.
 scope.$watch(
 scope.animationEnabled,
 function (isEnabled) {
 // This function will be called once on instantiation and then
 // again each time the value of the expression changes.
 // Use ''isEnabled'' in here to either enable or disable animation.
 console.log('Animation', isEnabled ? 'is enabled' : 'is disabled');
 }
 );
 }
 }
});

Хотя это не применимо к вашему приведенному примеру, давайте также рассмотрим другую технику потока данных, о которой я упоминал, где данные стекают по дереву.

В этом случае родительская директива может предоставить API дочерней директиве. Это, например, то, как директива ngModel взаимодействует с директивой родительской form или как ngSwitchWhen взаимодействует со своим родительским ngSwitch.

Ключевым здесь является свойство require в объявлении директивы, которое позволяет директиве зависеть от другой директивы либо от текущего элемента, либо от какого-либо родительского элемента. Ради этого примера мы будем искать его на любом родительском элементе.

Давайте сделаем надуманный пример родительской директивы со многими детьми, которую он хочет отслеживать по какой-то причине:

<parent>
 
 
 
</parent>

Сначала мы определяем parent директиву:

.directive('parent', function() {
 return {
 controller: function () {
 this.children = {};
 this.registerChild(name, child) {
 console.log('Got registration for child', name);
 this.children[name] = child;
 }
 }
 }
});

Директива для child - это то, где мы можем использовать механизм require:

.directive('child', function() {
 return {
 require: '^parent', // Must be nested inside a 'parent' directive
 link: function (scope, iElement, attrs, parentCtrl) {
 // Notice the extra 'parentCtrl' parameter above.

 // Provide an API for parent to interact with child.
 var child = {};
 child.doSomething = function () {
 console.log('Child', attrs.name, 'requested to do something');
 };

 parentCtrl.registerChild(attrs.name, child);
 }
 }
});

В этом случае мы устанавливаем двунаправленный канал связи между родителем и дочерним элементом, при этом дочерний элемент инициирует канал с использованием require и передает родительскому объекту объект, через который он может связываться с дочерним элементом. Когда require используются есть дополнительный аргумент, чтобы link давая контроллер директивы, которая была запрошена.

Наконец, расскажите о событиях. Они лучше всего применяются в ситуации, когда у вас есть одна директива (или, действительно, любой другой код, который владеет областью), который хочет передать конкретное уведомление тому, кто слушает. Например, $route связывается с ng-view (и любым другим, кто слушает), используя событие $routeChangeSuccess, а ng-view предупреждает остальную часть приложения, что представление готово через $viewContentLoaded.

Наблюдатели событий относятся к областям, а события распространяются вверх и вниз по иерархии областей.

Если вы занимаете область действия, вы можете следить за любыми событиями, которые могут проходить с использованием scope.$on:

scope.$on(
 '$viewContentLoaded',
 function () {
 console.log('view content loaded!');
 }
);

Если вы хотите отправить событие, вы можете отправить сообщение в иерархию областей с помощью $emit:

scope.$emit(
 'somethingHappened'
);

... или вы можете отправить сообщение вниз по иерархии областей с помощью $broadcast:

scope.$broadcast(
 'somethingHappened'
);

В некоторых случаях вы хотите передать мероприятие всему приложению, и в этом случае вы можете $broadcast на $rootScope:

$rootScope.$broadcast(
 'somethingHappened'
);

Одна важная вещь, которую следует учитывать в событиях, заключается в том, что они являются механизмом "точка-многоточка", то есть многие разные получатели могут "видеть" одно и то же сообщение. Это делает события довольно плохим механизмом для направленной коммуникации между двумя конкретными участниками, поэтому события следует использовать экономно.

Итак, есть обзор трех механизмов потока данных для директив в AngularJS. Для каждого есть разные компромиссы:

  • Передача данных в дочерние директивы по областям позволяет дочерней директиве отделяться от родителя, но требует, чтобы родительская директива (или, в вашем случае, контроллер) предоставляла данные, необходимые ребенку.
  • Механизм require лучше всего использовать для создания шаблонных конструкций, для которых требуется участие нескольких сильно связанных элементов, например, в случае ngSwitch где ngSwitchWhen существует только для использования с ngSwitch и без него бесполезно.
  • События лучше всего использовать для широких уведомлений, когда отправитель не особенно заботится о том, кто получает сообщение, а получатель не обязательно знает, кто его отправил. В этом случае отправитель и получатель действительно отделены друг от друга, и даже не может быть получателем.


.directive('chart', function() {
 return {
 scope: {
 toggle: "@" // pass as string - one way // you could also make this an attr if you want
 },
 ...
 controller: function($scope) {

 },
 link: function link(scope, element, attrs) {
 ...

 var toggleAnimation = function() {
 ...
 };

 // when you change this value, it will toggle the animation
 // logic which will check the values of this variable so you can do if statements and modify animations
 scope.$watch('toggle', function(newVal, oldVal){
 console.log(newVal);
 if(parseInt(newVal) === 1)
 toggleAnimation();
 else if(parseInt(newVal) === 0)
 ; // do something else like toggle back
 });
 }
 }
});

HTML

<div ng-controller="foo">
 
</div>

Контроллер JS

.controller('foo', function($scope) {
 // TODO: call chart toggleAnimation
 $scope.myVariable = 0; // initialize to this value

 function clickSomething(){
 $scope.myVariable = 1; // change, hence fire animation
 }


});


Возможно, вы можете использовать $scope.$broadcast для трансляции события в дочернюю область в директиве. Директива будет слушать это, а затем запустить метод, когда он услышит правильное событие:

$scope.$broadcast("toggleAnimation", this.textToBroadcast);

Скриншот здесь:

http://jsfiddle.net/smaye81/q2hbnL5b/3/

licensed under cc by-sa 3.0 with attribution.