到现在为止,通过AngularJS指令系列你已经学到了很多指令关键知识,但是仍然没有了解到任何相关指令中控制器方面的内容。即使控制器与视图和路径有关,但它仍然可以被嵌入到AngularJS指令中。实际上,自定义指令中有很多场景可以用到控制器,并且通过使用控制器,可以减少代码量,可以使代码更易维护。当然,在指令中使用或者不使用控制器都可以,但如果你更喜欢使用现在所使用的相似技术构建视图,你将会发现控制器技术在很多场景都是很关键的。通过使用控制器,指令开始变得更像是“子视图”。
在这篇文章中,我们将完整的控制器应用到指令中,并展示它们在指令中所起的作用。让我们以一个没有使用控制器的指令开始,探讨下没有使用控制器的优点和缺点。
没有控制器的指令
指令提供了几种不同方法去渲染HTML,整合数据以及执行额外的任务。在指令需要操控大量DOM的情况下,使用link函数比较合适。下边是一个简单的link函数使用的例子:
(function() {var app = angular.module('directivesModule');app.directive('domDirective', function () {return {restrict: 'A',link: function ($scope, element, attrs) {element.on('click', function () {element.html('You clicked me!');});element.on('mouseenter', function () {element.css('background-color', 'yellow');});element.on('mouseleave', function () {element.css('background-color', 'white');});}};});}());
给这个指令添加控制器作用不大,因为这个指令的主要目标是处理事件和操控DOM。使用视图和控制器在指令中(例如使用ng-click在指令中)方式也可以完成同样的工作,但如果最终目的是进行DOM操作,就没有任何理由给指令添加一个控制器。
当你不仅仅只是操作DOM,还要给生成的HTML添加数据,处理事件等等。这种情况下,给指令添加一个控制器在有些情况下能够减小代码量、简化一些过程。为了更清楚的解释这些,我们看一个指令的例子。渲染一系列项到视图中,然后提供一个按钮,主要用于给这一系列项添加条目。下边是这个指令渲染视图后的结果:
有几种不同的方式可以实现渲染这类UI。一种典型的DOM为主的方法是使用link函数去搞定所有事情,就像下边指令中所示的。请注意,有很多方法可以实现这个,并且最终目的是生成这些DOM(实现方法越简单越好)。
(function() {var app = angular.module('directivesModule');app.directive('isolateScopeWithoutController', function () {var link = function (scope, element, attrs) {//Create a copy of the original data that’s passed in var items = angular.copy(scope.datasource);function init() {var html = '<button id="addItem">Add Item</button><div></div>';element.html(html);element.on('click', function(event) {if (event.srcElement.id === 'addItem') {addItem();event.preventDefault();}});}function addItem() {//Call external function passed in with &scope.add();//Add new customer to the local collectionitems.push({name: 'New Directive Customer'});render();}function render() {var html = '<ul>';for (var i=0,len=items.length;i<len;i++) {html += '<li>' + items[i].name + '</li>'}html += '</ul>'; element.find('div').html(html);}init();render(); };return {restrict: 'EA',scope: {datasource: '=',add: '&',},link: link};});}());
即使这段代码实现了功能,但它与jQuery插件的路子相似,并使用了我称之为“面向控制”方法,普通HTML标签名和ID使用的非常多。所有DOM操作都是手动进行的,在有些时候这样做是比较好的,但是这确实不是创建Angular应用的常规方法。DOM操作与scope混合在一起,显得比较混乱,尤其是指令数量越来越多的时候。
当点击按钮时,addItem()函数被调用,这个函数的作用是调用render()函数和孤立作用域中的add函数,render()函数生成一个<ul>标签和几个<li>标签。看起来这种方法没有什么问题,但是我并不喜欢这种给JavaScript中嵌入过多字符串的方法,因为它随时间增加,会导致代码维护的代价非常大。一个小的指令应该是易于维护的,当指令添加新特性时很容易。
有一个比较特别地方在这个代码中。当scope.add()被调用时,其所在的父作用域函数需要使用$scope.$apply()以更新作用域中的属性,因为调用add()方法时,并不在AngularJS上下文环境中,而是在普通的JavaScript上下文环境中。最后,指令并不类似于“子视图”的概念,这个在本系列开始已经说过-它仅仅是一类代码。控制器能在这个例子中发挥什么作用呢?接下来我们将研究它。
给指令增加控制器和视图
上边所示的指令示例,虽然可以达到目的,但是如果你想要写一个标准的AngularJS视图DOM,使用更多的面向数据方法而不是面向控制方法,那该怎么办呢?通过在指令中使用一个控制器和视图,你会觉得你是真正沿着AngularJS应用该走的路线前进。
下边的例子是对之前指令的一个重写,使用了一个控制器和一个简单的视图,这样代码看起来比较干净易读。
(function() {var app = angular.module('directivesModule');app.directive('isolateScopeWithController', function () {var controller = ['$scope', function ($scope) {function init() {$scope.items = angular.copy($scope.datasource);}init();$scope.addItem = function () {$scope.add();//Add new customer to directive scope$scope.items.push({name: 'New Directive Controller Item'});};}],template = '<button ng-click="addItem()">Add Item</button><ul>' +'<li ng-repeat="item in items">{{ ::item.name }}</li></ul>';return {restrict: 'EA', //Default in 1.3+scope: {datasource: '=',add: '&',},controller: controller,template: template};});}());
指令可以像下边这样使用:
Attribute: <div isolate-scope-with-controller datasource="customers" add="addCustomer()"></div>Element: <isolate-scope-with-controller datasource="customers" add="addCustomer()"></isolate-scope-with-controller>
可以看出来,上边的代码很像你写一个普通的视图,然后给它定义一个控制器。就像我在这篇文章开始提到的,这种方法就像是在写一个“子视图”,因为在视图代码中,核心是数据而并非控制。视图利用AngularJS指令的优势去处理各种复杂的渲染任务,大量减少了之前必须写的DOM代码。
视图使用template属性,控制器使用controller属性。请牢记视图也可以通过templateUrl属性或者来自$templateCache,并不是一定要在指令中嵌入视图代码。当你的视图有非常多的HTML代码,并且你不想嵌入指令时,templateUrl或$templateCache将会解决这个问题。
As mentioned, the code in the view leverages existing AngularJS directives such asng-clickandng-repeatand also uses{{ … }}data binding expressions. This eliminates the DOM code shown earlier in the DOM-centric/control-oriented directive. The controller has the$scopeinjected as you’d expect and uses it to define anitemsproperty which is consumed byng-repeatin the view to generate <li> tags. As the button in the view is clicked theaddItem()function on the$scopeis invoked which calls theaddisolate scope property and adds a new item object into the local collection (since angular.copy() is used items added into the local collection won’t show up in the parent scope). BecauseaddItem()is called usingng-click, the parent scope call that is made ($scope.add()) won’t need to worry about using$scope.$apply()as mentioned in the earlier section.
In situations where a directive is being written with raw performance in mind then the DOM-centric approach shown earlier may be preferred I realize since you’d be purposely taking over control of the HTML that’s generated and avoiding the use of Angular directives. If you ever attend one of my conference sessions or training classes you’ll often hear me say, “Use the right tool for the right job”. I’ve never believed that “one size fits all” and know that each situation and application is unique.
This thought process definitely applies to directives since there are many different ways to write them. In many situations I’m happy with how AngularJS performs and know about the pitfalls to avoid so I prefer the controller/view type of directive whenever possible. It makes maintenance much easier down the road since you can leverage existing Angular directives in the directive’s view and modify the view using a controller and scope. If, however, I was trying to maximize performance and eliminate the use of directives such as ng-repeat then going the DOM-centric route with thelinkfunction might be a better choice. Again, choose the right tool for the right job.
如前所述,视图的这段代码。。。
原文地址:/dwahlin/creating-custom-angularjs-directives-part-6-using-controllers