Как вывести поведение из директивы с изолированной областью?
Как я могу выставить метод из директивы? Я знаю, что я должен использовать атрибуты для данных, но я действительно хочу разоблачить поведение, а не данные. То, что может вызвать родительский контроллер.
Скажем, мой 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
определяется по охвату пользователя и недоступен для родителя.
В моем случае я считаю, что загрязнение родительской области - плохая идея, и я действительно хотел бы избежать ее.
Итак, как я могу выставить функцию из директивы в родительский контроллер? Или: Как я могу вызвать метод для директивы родительского контроллера?
Ответы
Ответ 1
Вы можете сделать это с изолированной областью, настроив переменную в области, которая привязана к контроллеру с двух сторон (с использованием "=" ). В вашей директиве вы можете назначить функцию этой переменной, а 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>
JS:
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");
}
}
};
});
Ответ 2
Вместо того, чтобы пытаться выяснить, как вызвать функцию, скрытую внутри директивы, я думаю, вы должны спросить себя: почему я хочу вызвать функцию, определенную в директиве?
Одна из причин, по которой я могу думать, - это вызвать некоторое поведение директивы, которое также может быть вызвано пользователем приложения изнутри этой директивы.
Если это так, то очевидная и зависящая от него задача состояла бы в том, чтобы транслировать событие в области, содержащей директиву, которая должна реагировать на нее. Тогда директива будет слушать это событие и инициировать его функцию сама по себе.
Это дает дополнительные преимущества:
- вы можете отправлять параметры в объект данных события, если хотите
- вы можете вызвать реакцию в нескольких директивах (например, если они образуют коллекцию)
- вы можете связываться с директивой, которая находится глубоко в иерархии областей (например, если директива находится внутри другой директивы, которая находится внутри других директив и т.д.), не передавая функцию обратного вызова через каждую вложенную директиву
- он не нарушает выделение директивы
Пример
Попробуем придумать очень простой пример: предположим, у нас есть виджет, который отображает случайную вдохновляющую цитату, загруженную откуда-то. Он также имеет кнопку, чтобы изменить цитату на другую.
Здесь шаблон директивы:
<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();
});
Таким образом, мы создали односторонний канал связи от контроллера к директиве, которая не нарушает изоляцию директивы, а также работает, если директива глубоко вписана в иерархию областей кода, которая транслирует событие.
Ответ 3
Как я могу выставить функцию из директивы родительскому контроллеру?
Или: Как я могу вызвать метод для директивы родительского контроллера?
Ну, я не думаю, что вы должны пытаться это сделать (например, поведение контроллера связи с директивой), но если вы должны... здесь один из способов сделать это: передайте функцию контроллера в свою директиву, который директива может вызвать для уведомления диспетчера директивной функции:
<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
Ответ 4
Чтобы внести свой вклад, @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>
Ответ 5
В выпуске AngularJS V1.7.1 * введена новая директива ng-ref.
Атрибут ng-ref указывает AngularJS опубликовать контроллер компонента в текущей области. Это полезно для того, чтобы такой компонент, как аудиоплеер, предоставлял свой API-интерфейс родственным компонентам. Его элементы управления воспроизведением и остановкой легко доступны.
Для получения дополнительной информации см.