我有一个指令,这是代码:
.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()的最佳方法是什么?
尽管将对象暴露在指令的孤立作用域上以方便与之通信可能很有吸引力,但这样做可能会导致令人困惑的“意大利面条”代码,特别是当你需要通过几个级别(控制器、指令、嵌套指令等)链接这种通信时。
我们最初是沿着这条路走的,但经过更多的研究后发现,它更有意义,并导致更可维护和可读的代码,以公开事件和属性,指令将通过服务进行通信,然后在指令中的服务属性上使用$watch或任何其他控件,这些控件需要对通信的这些变化做出反应。
这种抽象与AngularJS的依赖注入框架配合得非常好,因为你可以将服务注入到任何需要对这些事件做出反应的项中。如果你看一下Angular.js文件,你会发现里面的指令也以这种方式使用services和$watch,它们不会在孤立的作用域中公开事件。
最后,如果你需要在相互依赖的指令之间通信,我建议在这些指令之间共享一个控制器作为通信手段。
AngularJS的维基最佳实践也提到了这个:
Only use .$broadcast(), .$emit() and .$on() for atomic events
Events that are relevant globally across the entire app (such as a user authenticating or the app closing). If you want events specific to modules, services or widgets you should consider Services, Directive Controllers, or 3rd Party Libs
$scope.$watch() should replace the need for events
Injecting services and calling methods directly is also useful for direct communication
Directives are able to directly communicate with each other through directive-controllers
假设动作按钮使用与指令相同的控制器$scope,只需在链接函数内的$scope上定义函数updateMap。然后,当单击操作按钮时,控制器可以调用该函数。
<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的评论,如果指令使用孤立的作用域,事情就更复杂了。这里有一种让它工作的方法:给map指令添加一个set-fn属性,它将在控制器上注册指令函数:
<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;
};
}
小提琴
有点晚了,但这是一个具有隔离作用域和在指令中调用函数的“事件”的解决方案。这个解决方案的灵感来自satchmorun的这篇SO帖子,并添加了一个模块和一个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();
});
}
}
});
(也许你必须添加功能来删除事件。)
在指令中设置MapAPI的引用并添加$scope。MapApi. updateMap作为事件。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();
};
}
你可以把方法名告诉指令来定义你想从控制器调用哪个,但没有隔离作用域,
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>
如果你想使用隔离作用域,你可以使用控制器作用域变量的双向绑定=来传递一个控制对象。你也可以用相同的控制对象控制页面上相同指令的几个实例。
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>