指令是运行在特定 dom 元素上的函数,用来扩展元素的功能。javascript
一个简版的 directive 的形式是这样的html
app.directive('myDirective', myDirective); myDirective.$inject = []; function myDirective(){ return { restrict: "AE", template: '' scope: {}, link: function(){} } }
下面介绍一些经常使用的选项。java
表示指令在 dom 中以何种形式被声明angularjs
E 元素segmentfault
A 属性(默认值)数组
C 类名缓存
M 注释(不建议使用)app
属性(E)是最经常使用的声明指令方式,由于能兼容老版本IE,且不引入新标签。元素(A)方式适合建立一个彻底独立、完整的组件。dom
指令优先级,默认为 0(最高1000,好比 ng-repeat ),若是须要同一元素上一个指令比另外一个指令先被调用,可给它设置更高的优先级。异步
在 Angular 1.2 里面,若是使用 checkbox,同时绑定 ng-model
和 ng-click
,而后根据 ng-model
的值在 ng-click
里进行相应的处理,像下面这样:
<input type="checkbox" ng-model="formData.status" ng-click="check()">
$scope.check = function(){ if($scope.formData.status){ alert('true'); }else{ alert('false'); } }
会发现,ng-model
的值和 check 里面的逻辑是相反的。其实这是由于在 Angular 1.2 里面,ng-click
的优先级比 ng-model
高,致使在值更新前,ng-click
就被触发。修复办法是用 ng-change
替换 ng-click
。在 Angular 1.3 以上版本已经修复了这个问题。
HTML 模版文件。
在调用指令后,模版文件是经过 XHR 来加载的,加载后会缓存到 $templateCache 服务中。
经过 AJAX 异步加载大量的模版会拖慢一个 Angular 应用的速度。能够提早将模版缓存到一个定义模版的 js 文件中,或者在加载 ng-app
的首页同时将模版以 script
标签加载,而后在配置 template 的地方经过 js 获取。
<script id="pagerTpl" type="text/html"> <div>...</div> </script> template: document.querySelector('#pagerTpl').innerHTML
当指令定义的标签内部也有标签的时候,transclude
设置为 true
可让子标签的内容保持不变。
transclude
一般用来建立可复用的组件,能够将整个模版,包括其中的指令经过 transclude
所有传入一个指令中。典型的应用是模态对话框或导航栏。
replace
默认为 false
,表示模版会被看成子元素插入到调用此指令的元素内部。
设置为 true
,则指令会被模版替换掉。
scope
默认为 false
,表示指令内和指令外共用同一做用域。设置为 true
的时候,表示从父做用域继承并建立一个新的做用域对象(是否是感受很像 ng-show 和 ng-if 的区别),不过这两种状况用的比较少(做用域污染)。
最经常使用的经过一个空对象 {}
,产生一个隔离做用域,用于建立可复用的组件。组件能够在未知上下文中使用,而且组件所处的外部做用域和内部做用域不会被不经意地污染。
但大多时候并不会直接使用无数据的隔离做用域。有三种办法让内部的隔离做用域同外部的做用域进行数据绑定。
=
是最经常使用的方式,经过它能够将本地做用域的属性同父级做用域上的属性进行双向绑定。
在元素中使用属性的方式为(不可使用 {% raw %}{{}}
{% endraw %}):
<div my-directive age="age"></div> scope: { age: '=' }
使用 @
能够将本地做用域同 dom 属性的值进行单向绑定,在使用父做用域中的值对指令中的属性初始赋值后,指令中属性值的修改不会影响父做用域中的值。
在元素中的使用方式为(有 {% raw %}{{}}
{% endraw %}):
<div my-directive my-name="{{name}}"></div> // 视图中的 - 链接的属性转换到指令定义中要使用驼峰式 scope: { name: '@myName' }
这是一个绑定函数方法的前缀标识符,经过 & 能够对父级做用域进行绑定,以便在其中运行函数。
在元素中的使用方式为:
<div my-directive change="changeName()"></div> scope: { changeName: '&change' }
更详细的栗子参见 这篇文章 的讲解。
通常状况下不须要使用指令的 controller
,只要使用 link
就够了,controller
和 link
函数能够互换。但它是在预编译阶段执行的(先于 compile
)。
用 controller
的场景是该指令(a)会被其余指令(b)require 的时候,在 b 的指令里能够传入 a 的这个 controller
,目的是为了指令间的复用和交流。而 link
只能在指令内部中定义行为,没法作到这样。
controller 的形式是这样的:
controller: function($scope, $ele, $attrs, $transclude){ ... }
通常状况下不须要使用 require
,它是和 controller
结合用的。require 参数用来引入其余指令或一个指令数组,并将引入指令的 controller 传给 link 函数的第四个参数。
AngularJS 应用启动后,会经历两个阶段。一是编译阶段(compile
),对模版 dom
进行转换(指令标签解析和变换),二是连接阶段(link
),将做用域和 dom
进行连接(数据绑定)。
在编译阶段,AngularJS 会遍历整个 HTML 文档并根据 JavaScript 中的指令定义来处理页面上声明的指令。
编译阶段能够改变原始的 dom
(template element
),因为此阶段还未对 dom
树进行数据绑定,因此此时对 dom
树操做只须要较少的性能开销。好比 ng-repeat
(改变原始 dom
生成多个 dom
节点) 和 ng-transclude
(嵌入模版到指令中) 都是在这个阶段对 dom
进行操做。
compile
后,返回一个函数(link
)或对象(preLink
和 postLink
)。
compile 函数形式:
/** * Compile function * * @param tElem - template element * @param tAttrs - attributes of the template element */ function(tEle, tAttrs){ ... }
连接阶段完成做用域和 dom
之间数据的绑定,dom
事件监听器的注册,也是在这个阶段作。
link 函数形式:
/** * pre-link and post-link function * * @param scope - scope associated with this instance * @param iElem - instance element * @param iAttrs - attributes of the instance element */ function(scope, iElem, iAttrs, ctrl){ ... }
若是指令定义中有 require
选项,link
的函数签名中还会有第四个参数,表明控制器或所依赖指令的控制器。
compile 是对指令的模板进行转换,link 是在模型和视图之间创建关联(包括注册事件监听)
compile 对同一个指令的多个实例只会执行一次,link 对于指令的每一个实例都会执行一次
compile 和 link 是互斥的,编写了 compile,自定义的 link 将无效
通常状况下只须要编写 link
举个简单的行内编辑的栗子,默认显示,双击可编辑。
directive 文件
app.directive('inlineEdit', inlineEdit); inlineEdit.$inject = ['$document']; function inlineEdit($document){ return { restrict: 'A', templateUrl: 'inline-edit.html', scope: { desc: '=ngModel' }, link: function(scope, ele, attrs){ scope.isEdit = false; ele.on('dblclick', function(){ scope.isEdit = true; scope.$digest(); }) $document.on('click', function(evt){ var src = evt.srcElement || evt.target, parent = src.parentNode; if(parent.classList && parent.classList[0] == 'inline-container'){ return; } scope.isEdit = false; scope.$digest(); }) } } }
template 文件
<div class="inline-container" ng-show="isEdit"> <input type="text" ng-model="desc"> </div> <div ng-if="!isEdit"> {{desc}} </div>
而后,能够这样使用
<div ng-model="desc" inline-edit></div>