it-roy-ru.com

Как выявить поведение из директивы с изолированной областью действия?

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

Допустим, мой DOM выглядит так:

<div ng-app="main">
    <div ng-controller="MyCtrl">
        <button ng-click="call()" >Call</button>
        <div id="container" my-directive> </div>
    </div>
</div>

JavaScript:

angular.module("main", []).controller("MyCtrl", function($scope) {
    $scope.call = function() {
        $scope.myfn();
    };
}).directive("myDirective", function() {
    return {
        // scope: {},
        controller: function($scope) {
            $scope.myfn = function() {
                console.log("myfn called");
            }
        }
    };
});

jsFiddle: http://jsfiddle.net/5gDjQ/7/

Если scope закомментирован (то есть директива не имеет изолированной области видимости), он работает просто отлично. Когда я нажимаю кнопку, вызывается myfn и регистрируется в консоли.

Как только я раскомментирую scope, это не сработает. myfn определен в дочерней области и не легко доступен для родителя.

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

Итак, как я могу представить функцию из директивы на родительский контроллер? Или: Как я могу вызвать метод по директиве из родительского контроллера?

34
Konrad Garus

Вы можете сделать это с изолированной областью, установив переменную в области, которая двухсторонне связана с контроллером (используя '='). Затем в вашей директиве вы можете назначить функцию этой переменной, и angular будет использовать привязку, чтобы найти соответствующую переменную в вашем контроллере. Эта переменная будет указывать на функцию, которую может вызвать ваш контроллер.

http://jsfiddle.net/GWCCr/

html: обратите внимание на новый атрибут:

<div ng-app="main">
    <div ng-controller="MyCtrl">
        <button ng-click="call()" >Call</button>
        <div id="container" my-directive my-fn="fnInCtrl"> </div>
    </div>
</div>

яШ:

angular.module("main", []).controller("MyCtrl", function($scope) {
    $scope.call = function() {
        $scope.fnInCtrl();
    };
}).directive("myDirective", function() {
    return {
        scope: {
            myFn: '='
        },
        controller: function($scope) {
            $scope.myFn = function() {
                console.log("myfn called");
            }
        }
    };
});
26
Roy Truelove

Вместо того, чтобы пытаться понять, как вызвать функцию, скрытую внутри директивы, я думаю, что вы должны спросить себя: Почему я хочу вызвать функцию, определенную в директиве?

Одна из причин, по которой я могу придумать: вызвать поведение директивы, которое также может быть вызвано пользователем приложения из директивы.

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

Это имеет дополнительные преимущества:

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

Пример

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

Вот шаблон директивы:

<p>{{ quote }}</p>
<button ng-click="refreshQuote()"></button>

А вот код директивы:

app.directive("randomQuote", function () {
  return {
    restrict: "E",
    scope: {},
    link: function (scope) {
      scope.refreshQuote = function () {
        scope.quote = ... // some complicated code here
      };
      scope.refreshQuote();
    }
  };
});

Обратите внимание, что директива полностью автономна: она имеет изолированную область видимости и сама выполняет выборку кавычек.

Предположим, мы также хотим иметь возможность обновить цитату из контроллера. Это может быть так же просто, как вызвать это в коде контроллера:

$scope.$broadcast("refresh-random-quote");

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

scope.$on("refresh-random-quote", function () {
  scope.refreshQuote();
});

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

16
DzinX

Как я могу выставить функцию из директивы на родительский контроллер?
Или: Как я могу вызвать метод по директиве из родительского контроллера?

Ну, я не думаю, что вы должны пытаться сделать это (то есть связать поведение контроллера с директивой), но если вы должны ... вот один из способов, которым вы можете это сделать: передать функцию контроллера в вашу директиву, которая Директива может вызывать для уведомления контроллера о функции директивы:

<div id="container" my-directive cb="setDirectiveFn(fn)"></div>

directive("myDirective", function() {
    return {
       scope: { cb: '&' },
        controller: function($scope) {
            $scope.myfn = function() {
                console.log("myfn called");
            }
            $scope.cb({fn: $scope.myfn});
        }
    };
});

Fiddle

7
Mark Rajcok

Чтобы внести свой вклад, @georgeawg дал мне классное решение, используя Service для выполнения этой работы. Таким образом, вы можете обрабатывать несколько директив на одной странице.

<html ng-app="myApp">
<head>
  <script src="https://opensource.keycdn.com/angularjs/1.6.5/angular.min.js"></script>
</head>
<body ng-controller="mainCtrl">
  <h1>^v1.6.0 ($postLink hook required)</h1>
  <my-directive name="sample1" number="number1"></my-directive>
  <my-directive name="sample2" number="number2"></my-directive>
</body>
<script>
  angular.module('myApp', [])
    .controller('mainCtrl', ['$scope', 'myDirectiveFactory', function ($scope, myDirectiveFactory) {
      $scope.number1 = 10
      $scope.number2 = 0
      this.$postLink = function () {
        myDirectiveFactory.get('sample2')
          .increment()
          .increment()
          .increment()
          .increment()
        myDirectiveFactory.get('sample1')
          .increment()
          .increment()
        myDirectiveFactory.get('sample2')
        .decrement()
      }
    }])
    .factory('myDirectiveFactory', function () {
      var instance = {}
      return {
        get: function (name) {
          return instance[name]
        },
        register: function (name, value) {
          return instance[name] = value
        },
        destroy: function (name) {
          delete instance[name]
        }
      }
    })
    .controller('myDirectiveCtrl', ['$scope', 'myDirectiveFactory', function ($scope, myDirectiveFactory) {
      $scope.name = $scope.name || 'myDirective'
      $scope.$on('$destroy', function () {
        myDirectiveFactory.destroy($scope.name)
      })
      var service = {
        increment: function () {
          $scope.number++
          return this
        },
        decrement: function () {
          $scope.number--
          return this
        }
      }
      myDirectiveFactory.register($scope.name, service)
    }])
    .directive('myDirective', [function () {
      return {
        controller: 'myDirectiveCtrl',
        restrict: 'E',
        scope: {
          number: '<',
          name: '@?'
        },
        template: '<p> {{ number }} </p>'
      }
    }])
</script>
</html>
1
saulsluz

Выпуск AngularJS V1.7.1* вводит новую директиву ng-ref .

Атрибут ng-ref указывает AngularJS публиковать контроллер компонента в текущей области видимости. Это полезно для того, чтобы такой компонент, как аудиоплеер, выставлял свой API на одноуровневых компонентах. Его элементы управления воспроизведением и остановкой легко доступны.

Для получения дополнительной информации см.

0
georgeawg