Directive是教HTML玩一些新把戏的途径。在DOM编译期间,directives匹配HTML并执行。这允许directive注册行为或者转换DOM结构。
Angular自带一组内置的directive,对于建立Web App有很大帮助。继续扩展的话,可以在HTML定义领域特定语言(domain specific language ,DSL)。
一、在HTML中引用directives
Directive有驼峰式(camel cased)的风格的命名,如ngBind(放在属性里貌似用不了~)。但directive也可以支蛇底式的命名(snake case),需要通过:(冒号)、-(减号)或_(下划线)连接。作为一个可选项,directive可以用“x-”或者“data-”作为前缀,以满足HTML验证需要。这里列出directive的合法命名:
- ng:bind
- ng-bind
- ng_bind
- x-ng-bind
- data-ng-bind
Directive可以放置于元素名、属性、class、注释中。下面是引用myDir这个directive的等价方式。(但很多directive都限制为“属性”的使用方式)
<span my-dir="exp"></span> <span class="my-dir: exp;"></span> <my-dir></my-dir> <!-- directive: my-dir exp --></div>
Directive可以通过多种方式引用,下面列出N种等价的方式:
<!DOCTYPE HTML> <html lang="zh-cn" ng-app> <head> <meta charset="UTF-8"> <title>invoke-directive</title> <style type="text/css"> .ng-cloak { display: none; } </style> </head> <body> <div ng-controller="MyCtrl"> Hello <input ng-model="name"/><hr/> ngBind="name" 这个用不了~~ <span ngBind="name"></span><br/> ng:bind="name"<span ng:bind="name"></span><br/> ng_bind="name"<span ng_bind="name"></span><br/> ng-bind="name"<span ng-bind="name"></span><br/> data-ng-bind="name"<span data-ng-bind="name"></span><br/> x-ng-bind="name"<span x-ng-bind="name"></span><br/> </div> <script src="../angular-1.0.1.js" type="text/javascript"></script> <script type="text/javascript"> function MyCtrl($scope) { $scope.name = "beauty~~"; } </script> </body> </html></div>
二、String interpolation
在编译过程中,compiler通过$interpolate服务匹配文本与属性中的嵌入表达式(如{{something}})。这些表达式将会注册为watches,并且作为digest cycle(之前不是digest-loop吗?!)的一部分,一同更新。下面是一个简单的interpolation:
<img src="img/{{username}}.jpg"/>Hello {{username}}!
三、Compilation process, and directive matching
HTML“编译”的三个步骤:
1. 首先,通过浏览器的标准API,将HTML转换为DOM对象。这是很重要的一步。因为模版必须是可解析(符合规范)的HTML。这里可以跟大多数的模版系统做对比,它们一般是基于字符串的,而不是基于DOM元素的。
2. 对DOM的编译(compilation)是通过调用$comple()方法完成的。这个方法遍历DOM,对directive进行匹配。如果匹配成功,那么它将与对应的DOM一起,加入到directive列表中。只要所有与指定DOM关联的directive被识别出来,他们将按照优先级排序,并按照这个顺序执行他们的compile() 函数。directive的编译函数(compile function),拥有一个修改DOM结构的机会,并负责产生link() function的解析。$compile()方法返回一个组合的linking function,是所有directive自身的compile function返回的linking function的集合。
3. 通过上一步返回的linking function,将模版与scope连接起来。这反过来会调用directive自身的linking function,允许它们在元素上注册一些监听器(listener),以及与scope一起建立一些watches。这样得出的结果,是在scope与DOM之间的一个双向、即时的绑定。scope发生改变时,DOM会得到对应的响应。
var $compile = ...; // injected into your code var scope = ...; var html = '<div ng-bind='exp'></div>'; // Step 1: parse HTML into DOM element var template = angular.element(html); // Step 2: compile the template var linkFn = $compile(template); // Step 3: link the compiled template with the scope. linkFn(scope);</div>
四、Reasons behind the compile/link separation
在这个时候,你可能会想知道为什么编译进程会划分为compile和linke两个步骤。为了明白这一点,让我们看看一个真实的例子(repeater)
Hello {{user}}, you have these actions: <ul> <li ng-repeat="action in user.actions">{{action.description}}</li> </ul></div>
简单地讲,之所以分开compile和linke两步,是因为有时候需要在model改变后,对应的DOM结构也需要改变的情况,如repeaters。
当上面的例子被编译时,编译器会遍历所有节点以寻找directive。{{user}}是一个interpolation directive的例子。ngRepeat又是另外一个directive。但ngRepeat有一个难点。它需要能够很快地为每一个在users.actions中的action制造出新的li的能力。这意味着它为了满足克隆li并且嵌入特定的action(这里是指user的actions的其中一个值)的目的,需要保持一个干净li元素的拷贝,li元素需要被克隆和插入ul元素。但仅仅克隆li元素是不够的。还需要编译li,以便它的directive({{action.descriptions}})能够在正确的scope中被解析。原始的方法,一般会简单地插入一个li元素的拷贝,然后编译它。但编译每一个li元素的拷贝会比较缓慢,因为编译过程需要我们遍历DOM节点树,查找directive并运行它们。如果我们有一个编译,需要通过repeater对100个item进行处理,那么我们将陷入性能问题。
问题的解决方案,是将编译过程分解为两个步骤。compile阶段识别出所有directive,并且将它们按照优先级进行排序,在linking阶段将特定的scope与特定的li绑定在一起。
ngRepeat将各个li分开编译以防止编译过程落入li元素中。li元素的编译结果是一个包含所有包含在li元素中的directive的linking function,准备与特定li元素的拷贝进行连接。在运行时,ngRepeat监测表达式,并作为一个item,被加入到一个li元素拷贝的数组,为克隆好的li元素创建新的scope,并调用该拷贝对应的link function。
总结:
1.编译函数(compile function) - 编译函数在directive中是比较少见的,因为大多数directive只关心与指定的DOM元素工作,而不是改变DOM元素的模版(DOM自身以及内部的结构)。为了优化性能,一些可以被directive实例共享的操作,可以移动到compile函数中。
2.连接函数(link function) - 极少directive是没有link function的。link function允许directive在指定的拷贝后的DOM元素实例上注册监听器,也可以将scope中特定内容复制到DOM中。
五、写一个directive(简易版)
在这个例子里面,我们将建立一个根据输入格式,显示当前时间的directive。
<!DOCTYPE HTML> <html lang="zh-cn" ng-app="TimeFormat"> <head> <meta charset="UTF-8"> <title>time-format</title> </head> <body> <div ng-controller="MyCtrl" id="main"> Date format: <input ng-model="format" type="text"/><hr/> <!--下面使用属性x-current-time,是为了试试上面说的合法命名~~current:time、current-time、current_time、data-current-time -_-!!! --> Current time is : <span x-current-time="format" id="myFormat"></span><br/> <button ng-click="remove()">remove the span</button> </div> <script src="../angular-1.0.1.js" type="text/javascript"></script> <script type="text/javascript"> angular.module("TimeFormat", []) //在TimeFormat应用中注册“currentTime”这个directive的工厂方法 //前文提到过,依赖注入,可以直接在function的参数中写入,这里注入了$timeout、dataFilter .directive("currentTime", function (dateFilter) { //这个是上面提到的linking function。(不需要添加compile function,为啥?。。。) return function (scope, element, attr) {