У меня есть директива, вот код:
.directive('map', function() {
return {
restrict: 'E',
replace: true,
template: '<div></div>',
link: function($scope, element, attrs) {
var center = new google.maps.LatLng(50.1, 14.4);
$scope.map_options = {
zoom: 14,
center: center,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
// create map
var map = new google.maps.Map(document.getElementById(attrs.id), $scope.map_options);
var dirService= new google.maps.DirectionsService();
var dirRenderer= new google.maps.DirectionsRenderer()
var showDirections = function(dirResult, dirStatus) {
if (dirStatus != google.maps.DirectionsStatus.OK) {
alert('Directions failed: ' + dirStatus);
return;
}
// Show directions
dirRenderer.setMap(map);
//$scope.dirRenderer.setPanel(Demo.dirContainer);
dirRenderer.setDirections(dirResult);
};
// Watch
var updateMap = function(){
dirService.route($scope.dirRequest, showDirections);
};
$scope.$watch('dirRequest.origin', updateMap);
google.maps.event.addListener(map, 'zoom_changed', function() {
$scope.map_options.zoom = map.getZoom();
});
dirService.route($scope.dirRequest, showDirections);
}
}
})
Я хотел бы называть updateMap()
действием пользователя. Кнопка действия не указана в директиве.
Каков наилучший способ вызова updateMap()
с контроллера?
Если вы хотите использовать изолированные области, вы можете передать объект управления с помощью двунаправленной привязки =
переменной из области управления. Вы также можете управлять несколькими экземплярами одной и той же директивы на странице с одним и тем же объектом управления.
angular.module('directiveControlDemo', [])
.controller('MainCtrl', function($scope) {
$scope.focusinControl = {};
})
.directive('focusin', function factory() {
return {
restrict: 'E',
replace: true,
template: '<div>A:{{internalControl}}</div>',
scope: {
control: '='
},
link: function(scope, element, attrs) {
scope.internalControl = scope.control || {};
scope.internalControl.takenTablets = 0;
scope.internalControl.takeTablet = function() {
scope.internalControl.takenTablets += 1;
}
}
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="directiveControlDemo">
<div ng-controller="MainCtrl">
<button ng-click="focusinControl.takeTablet()">Call directive function</button>
<p>
<b>In controller scope:</b>
{{focusinControl}}
</p>
<p>
<b>In directive scope:</b>
<focusin control="focusinControl"></focusin>
</p>
<p>
<b>Without control object:</b>
<focusin></focusin>
</p>
</div>
</div>
Предполагая, что кнопка действия использует в качестве директивы один и тот же контроллер $scope
, просто определите функцию updateMap
на $scope
внутри функции связи. Затем ваш контроллер может вызвать эту функцию при нажатии кнопки действия.
<div ng-controller="MyCtrl">
<map></map>
<button ng-click="updateMap()">call updateMap()</button>
</div>
app.directive('map', function() {
return {
restrict: 'E',
replace: true,
template: '<div></div>',
link: function($scope, element, attrs) {
$scope.updateMap = function() {
alert('inside updateMap()');
}
}
}
});
В соответствии с комментарием @FlorianF, если директива использует изолированную область, все сложнее. Здесь один из способов заставить его работать: добавьте атрибут set-fn
в директиву map
, который зарегистрирует директивную функцию с контроллером:
<map set-fn="setDirectiveFn(theDirFn)"></map>
<button ng-click="directiveFn()">call directive function</button>
scope: { setFn: '&' },
link: function(scope, element, attrs) {
scope.updateMap = function() {
alert('inside updateMap()');
}
scope.setFn({theDirFn: scope.updateMap});
}
function MyCtrl($scope) {
$scope.setDirectiveFn = function(directiveFn) {
$scope.directiveFn = directiveFn;
};
}
Хотя может возникнуть соблазн разоблачить объект в изолированной области действия директивы, чтобы облегчить связь с ним, выполнение может привести к запутыванию кода "спагетти", особенно если вам необходимо связать это сообщение через пару уровней (контроллер, директиву, вложенную директиву и т.д.)
Мы изначально пошли по этому пути, но после нескольких исследований выяснилось, что это имеет больше смысла и привело к тому, что как более удобный, так и читаемый код выведет события и свойства, которые директива будет использовать для связи через службу, а затем используя $watch на этом свойства службы в директиве или любые другие элементы управления, которые должны будут реагировать на эти изменения для связи.
Эта абстракция очень хорошо работает с инфраструктурой инъекций зависимостей AngularJS, так как вы можете вводить эту услугу в любые элементы, которые должны реагировать на эти события. Если вы посмотрите на файл Angular.js, вы увидите, что в этих директивах также используются службы и $watch таким образом, они не выставляют события по изолированной области.
Наконец, в случае, если вам нужно общаться между директивами, которые зависят друг от друга, я бы рекомендовал использовать контроллер между этими директивами в качестве средства связи.
Угловая версия Wiki для лучшей практики также упоминает следующее:
Используйте только $broadcast(),. $emit() и. $on() для атомных событий События, которые имеют глобальное значение во всем приложении (например, аутентификация пользователя или закрытие приложения). Если вам нужны события, относящиеся к модулям, службам или виджетам, вам следует рассмотреть Службы, директивные контроллеры или сторонние лидеры.
- $scope. $watch() должен заменить необходимость событий
- Инъекционные услуги и методы вызова напрямую также полезны для прямой связи
- Директивы могут напрямую взаимодействовать друг с другом через директивные контроллеры
=
, переменная содержит имя метода и аргументы. (2) выставить строку одностороннего связывания @
качестве идентификатора темы и позволить вызываемому абоненту отправить событие по этой теме. Теперь я видел лучшие практики вики. Я думаю, что есть причина не делать это в любом случае. Но мне все еще не очень понятно, как это работает. В моем случае я создал директиву tabset, я хочу предоставить switchTab(tabIndex)
. Не могли бы вы привести пример?
switchTab(tabIndex)
, вы бы связывали только переменную tabIndex
. Ваш контроллер страницы может иметь действия, которые изменяют эту переменную. Вы связываете / передаете эту переменную в свою вкладку Directive. Ваша директива Tab может затем отслеживать эту переменную на предмет изменений и выполнять switchTab по своему усмотрению. Потому что директива решает, когда и как управлять своими вкладками на основе переменной. Это не работа внешнего источника, в противном случае внешние источники требуют знания внутренней работы директивы, что плохо.
Основываясь на ответе Оливера - вам может не всегда понадобиться доступ к внутренним методам директивы, и в этих случаях вы, вероятно, не хотите создавать пустой объект и добавлять директиву control
в директиву только для предотвращения он отбрасывает ошибку (cannot set property 'takeTablet' of undefined
).
Вы также можете использовать этот метод в других местах в пределах директивы.
Я бы добавил проверку, чтобы убедиться, что scope.control
существует, и установите для него методы аналогично раскрывающемуся шаблону модуля
app.directive('focusin', function factory() {
return {
restrict: 'E',
replace: true,
template: '<div>A:{{control}}</div>',
scope: {
control: '='
},
link : function (scope, element, attrs) {
var takenTablets = 0;
var takeTablet = function() {
takenTablets += 1;
}
if (scope.control) {
scope.control = {
takeTablet: takeTablet
};
}
}
};
});
Немного поздно, но это решение с изолированной областью действия и "событиями" для вызова функции в директиве. Это решение освещается этим сообщением SO satchmorun и добавляет модуль и API.
//Create module
var MapModule = angular.module('MapModule', []);
//Load dependency dynamically
angular.module('app').requires.push('MapModule');
Создайте API для связи с директивой. AddUpdateEvent добавляет событие в массив событий, а updateMap вызывает каждую функцию события.
MapModule.factory('MapApi', function () {
return {
events: [],
addUpdateEvent: function (func) {
this.events.push(func);
},
updateMap: function () {
this.events.forEach(function (func) {
func.call();
});
}
}
});
(Возможно, вам нужно добавить funcitonality для удаления события.)
В директиве задается ссылка на MapAPI и добавляется $scope.updateMap как событие, когда вызывается MapApi.updateMap.
app.directive('map', function () {
return {
restrict: 'E',
scope: {},
templateUrl: '....',
controller: function ($scope, $http, $attrs, MapApi) {
$scope.api = MapApi;
$scope.updateMap = function () {
//Update the map
};
//Add event
$scope.api.addUpdateEvent($scope.updateMap);
}
}
});
В "основном" контроллере добавьте ссылку на MapApi и просто вызовите MapApi.updateMap(), чтобы обновить карту.
app.controller('mainController', function ($scope, MapApi) {
$scope.updateMapButtonClick = function() {
MapApi.updateMap();
};
}
Честно говоря, я не был действительно убежден ни в одном из ответов в этой теме. Итак, вот мои решения:
Этот метод не зависит от того, является ли директива $scope
общей или изолированной
A factory
для регистрации экземпляров директивы
angular.module('myModule').factory('MyDirectiveHandler', function() {
var instance_map = {};
var service = {
registerDirective: registerDirective,
getDirective: getDirective,
deregisterDirective: deregisterDirective
};
return service;
function registerDirective(name, ctrl) {
instance_map[name] = ctrl;
}
function getDirective(name) {
return instance_map[name];
}
function deregisterDirective(name) {
instance_map[name] = null;
}
});
В директивном коде я обычно ставил всю логику, которая не касается внутреннего контроллера DOM. И регистрируя экземпляр контроллера внутри нашего обработчика
angular.module('myModule').directive('myDirective', function(MyDirectiveHandler) {
var directive = {
link: link,
controller: controller
};
return directive;
function link() {
//link fn code
}
function controller($scope, $attrs) {
var name = $attrs.name;
this.updateMap = function() {
//some code
};
MyDirectiveHandler.registerDirective(name, this);
$scope.$on('destroy', function() {
MyDirectiveHandler.deregisterDirective(name);
});
}
})
код шаблона
<div my-directive name="foo"></div>
Доступ к экземпляру контроллера с помощью factory
и запуск общедоступных методов
angular.module('myModule').controller('MyController', function(MyDirectiveHandler, $scope) {
$scope.someFn = function() {
MyDirectiveHandler.get('foo').updateMap();
};
});
Взяв лист из angular книги о том, как они относятся к
<form name="my_form"></form>
используя $parse и регистрируя контроллер в области $parent
. Этот метод не работает над изолированными директивами $scope
.
angular.module('myModule').directive('myDirective', function($parse) {
var directive = {
link: link,
controller: controller,
scope: true
};
return directive;
function link() {
//link fn code
}
function controller($scope, $attrs) {
$parse($attrs.name).assign($scope.$parent, this);
this.updateMap = function() {
//some code
};
}
})
Доступ к нему внутри контроллера с помощью $scope.foo
angular.module('myModule').controller('MyController', function($scope) {
$scope.someFn = function() {
$scope.foo.updateMap();
};
});
$scope.foo
должен быть $scope.my_form
$scope.foo
так как наш шаблон <div my-directive name="foo"></div>
и значение атрибута name
равно 'foo'. <form
является лишь примером одной из директив углового, в которой используется этот метод
Вы можете указать атрибут DOM, который можно использовать, чтобы позволить директиве определять функцию в родительской области. Затем родительская область может вызвать этот метод, как и любой другой. Вот плукер. Ниже приведен соответствующий код.
clearfn
- это атрибут элемента директивы, в который родительская область может передать свойство области видимости, которое директива может затем установить на функцию, которая выполняет желаемое поведение.
<!DOCTYPE html>
<html ng-app="myapp">
<head>
<script data-require="angular.js@*" data-semver="1.3.0-beta.5" src="https://code.angularjs.org/1.3.0-beta.5/angular.js"></script>
<link rel="stylesheet" href="style.css" />
<style>
my-box{
display:block;
border:solid 1px #aaa;
min-width:50px;
min-height:50px;
padding:.5em;
margin:1em;
outline:0px;
box-shadow:inset 0px 0px .4em #aaa;
}
</style>
</head>
<body ng-controller="mycontroller">
<h1>Call method on directive</h1>
<button ng-click="clear()">Clear</button>
<my-box clearfn="clear" contentEditable=true></my-box>
<script>
var app = angular.module('myapp', []);
app.controller('mycontroller', function($scope){
});
app.directive('myBox', function(){
return {
restrict: 'E',
scope: {
clearFn: '=clearfn'
},
template: '',
link: function(scope, element, attrs){
element.html('Hello World!');
scope.clearFn = function(){
element.html('');
};
}
}
});
</script>
</body>
</html>
scope: { clearFn: '=clearfn' }
).
Вы можете указать имя метода директиве для определения того, что вы хотите вызвать из контроллера, но без выделения области,
angular.module("app", [])
.directive("palyer", [
function() {
return {
restrict: "A",
template:'<div class="player"><span ng-bind="text"></span></div>',
link: function($scope, element, attr) {
if (attr.toPlay) {
$scope[attr.toPlay] = function(name) {
$scope.text = name + " playing...";
}
}
}
};
}
])
.controller("playerController", ["$scope",
function($scope) {
$scope.clickPlay = function() {
$scope.play('AR Song');
};
}
]);
.player{
border:1px solid;
padding: 10px;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
<div ng-controller="playerController">
<p>Click play button to play
<p>
<p palyer="" to-play="play"></p>
<button ng-click="clickPlay()">Play</button>
</div>
</div>
Просто используйте область. $parent для связывания функции, вызываемой директивной функцией
angular.module('myApp', [])
.controller('MyCtrl',['$scope',function($scope) {
}])
.directive('mydirective',function(){
function link(scope, el, attr){
//use scope.$parent to associate the function called to directive function
scope.$parent.myfunction = function directivefunction(parameter){
//do something
}
}
return {
link: link,
restrict: 'E'
};
});
в HTML
<div ng-controller="MyCtrl">
<mydirective></mydirective>
<button ng-click="myfunction(parameter)">call()</button>
</div>
Ниже будет полезно, когда у вас есть контроллеры (как родительские, так и директивные (изолированные)) в формате "контроллер как"
кто-то может найти это полезным,
:
var directive = {
link: link,
restrict: 'E',
replace: true,
scope: {
clearFilters: '='
},
templateUrl: "/temp.html",
bindToController: true,
controller: ProjectCustomAttributesController,
controllerAs: 'vmd'
};
return directive;
function link(scope, element, attrs) {
scope.vmd.clearFilters = scope.vmd.SetFitlersToDefaultValue;
}
}
директива Контроллер:
function DirectiveController($location, dbConnection, uiUtility) {
vmd.SetFitlersToDefaultValue = SetFitlersToDefaultValue;
function SetFitlersToDefaultValue() {
//your logic
}
}
html-код:
<Test-directive clear-filters="vm.ClearFilters"></Test-directive>
<a class="pull-right" style="cursor: pointer" ng-click="vm.ClearFilters()"><u>Clear</u></a>
//this button is from parent controller which will call directive controller function
Как получить контрольный контроллер в контроллере страницы:
напишите специальную директиву, чтобы получить ссылку на директивный контроллер из элемента DOM:
angular.module('myApp')
.directive('controller', controller);
controller.$inject = ['$parse'];
function controller($parse) {
var directive = {
restrict: 'A',
link: linkFunction
};
return directive;
function linkFunction(scope, el, attrs) {
var directiveName = attrs.$normalize(el.prop("tagName").toLowerCase());
var directiveController = el.controller(directiveName);
var model = $parse(attrs.controller);
model.assign(scope, directiveController);
}
}
используйте его в контроллере страницы html:
<my-directive controller="vm.myDirectiveController"></my-directive>
Используйте контрольный контроллер в контроллере страницы:
vm.myDirectiveController.callSomeMethod();
Примечание: данное решение работает только для контроллеров директив элементов (имя тега используется для получения имени искомой директивы).
Возможно, это не лучший выбор, но вы можете сделать angular.element("#element").isolateScope()
или $("#element").isolateScope()
для доступа к области и/или контроллеру вашей директивы.