参考文章 :
http://www.zouyesheng.com/angular.html#toc20 18.自定义指令directive
http://blog.jobbole.com/62249/
http://blog.jobbole.com/62999/
https://docs.angularjs.org/api/ng/service/$compile
https://docs.angularjs.org/guide/directive
http://www.cnblogs.com/lvdabao/p/3398044.html
http://loveky.github.io/2014/04/01/angularjs-directivecompilelink/
directive 是angular 用于扩展html标签的方案.
它使得我们可以在模板上写入自定义的元素或标签,通过angular的扫描执行一些改装事件绑定等等。
angular 也提供了很多的指令比如 ng-click,ng-repeat 等等
首先大致说一下整个执行过程.
1.angular 调用$compile来偏离我们的html文档, 把dom和指令关联在一起打包起来
2.对每个element的指令排序
3.检查指令如果有template就replace等等,templateUrl 会发出异步请求(回来的时候应该其它dom已经做完了)
4.循环调用指令中的 compile,所以我们在compile可以调用$element修改模板 (这个时候模板还没有被插入到document中)
5.把每个compile返回的link函数收集起来
6.link函数可以分为 PRe 和 post (pre 和 post 最要区别在执行持续, pre 是从parent to child , post 则是 child to parent) 参考http://jsfiddle.net/loveky/K93SE/light/
7.循环调用 pre link , 并把scope传入, 这时我们的参数$attrs 中的值是还没解析的表达式 ,
8.循环执行 post link , 把scope传入,这时的$attrs 已经放入了$scope的值,我们可以做一些绑定监听等
9.调用$digest渲染.
最终就是美美的DOM啦 .
小总结 : $compile -> 运行全部节点指令的compile , 一个一个节点运行 pre link, 一直到子层完 , 调用post link 一直到parent , 去下一个兄弟节点,再次运行pre link... (ng-controller 是最优先的pre link)
我们现在一步一步来看具体实现
angular.module("app", [], angular.noop). directive("myelem1", function () { return { restrict : "E", link: function (scope, element, attrs, controllers) { } } });
这个是创建方法
retrun 一个对象,里面包含了指令的config等等 .
具体属性有 :
我们逐一介绍
restrict
用于表示我们的指令类型,支持4中pattern , 很好记叫ECMA
E = elemet <myelem></myelem> 元素
C = class <div class="myelem"> class 中的属性,基本不用
M = 注释 <!-- directive: my-dir exp --> 基本不用
A = attr <div myelem="??"> 标签
restrict = "ECMA" 可以同时放多个
template
template : "<div>123</div>"; 写模板,这里的模板和一般写的版本相同,没有任何限制
默认情况下,模板将被append进当前的指令元素
templateUrl
也可以使用模板地址代替string模板, 如果一个指令使用template的话,它会被异步编程,请求template时,angular会先解析其它element指令先
replace
replace : true 表示把模板直接替换掉当前的指令元素。这里有个限定,元素必须是一对一的对换,简单说就是模板必须被一层div包到完只能有子层不能有兄弟。
priority
priority : 100 放一个number , 在一个元素上设置指令的解析次序。如果2个指令相同,那么angular不会区分那个先执行。(越小的代表优先,0是default)
需要特别注意,当我们在一个element上放置了多个指令,会有一下的限制
isolated scope
. //要隔离的scopetransclusion
option. //不懂template
ortemplateURL
. //只能有一个指令用于带有模板scope
scope : false | true | {@attr: '引用节点属性', =attr: '把节点属性值引用成scope属性值', &attr: '把节点属性值包装成函数'}
false 表示直接使用指令位于的controller的$scope
true 表示new一个$scope继承controller $scope
{..} 表示new一个$scope不继承,但是可以通过 @=& 来和元素上的标签做桥梁,在标签上就可以和controller $scope的属性做一些连接了
这里特别说一下,如果是用继承,那么指令本身的scope是新的,如果用隔离scope,指令本身的scope不是新的,而指令模板的scope才是新的 (大家可以自己玩玩看^^)
那么来看看 @=&的区别和用法。
场景是一个 ctrl 和 一个 dir
如果我们想做一个单向绑定, 即 ctrl 传一个值给 dir, 之后只有 ctrl一方的值能同步到 dir.我们使用 "@" (即使值是个object,它依然是单向绑定,angular传入的不是引用)
如果我们想做双向绑定,即 dir 的值也能同步到 ctrl . 我们使用 "="
如果我们想传的是方法,我们使用 "&"
注意 : “&” 也是单向绑定的。如果是要穿个动态方法,我们可以使用 "=" 把方法当值来传也是可以的。
另外对于依赖跟踪属性,我们也可以把它们当方法来看待,使用 "&"
新手 : 不要以为能用"@"的地方就能用"=",这是不对的概念,会遇到很多坑的。
下面我们指出了一些小区别,要留意
<mydir single-name="{{singleName}}" //@ 要使用 {{ 属性名 }} , 如果没有{{}}表示它是个值,之后不会有任何同步绑定。 double-name="doubleName" //= 只写属性名就好 click="click()"> "&" 要使用 属性名()</mydir>scope: { singleName: "@", doubleName: "=", click: "&"},//在link函数中log(attr.singleName); //使用 @ 这里是"keatkaet1" <-获取到值 , 使用 = 这里是"doubleName" <-只是属性名 , 使用 & 这里是 "click()" <- 属性名()
terminal
terminal : true 表示在一个元素上priority大于这个指令的指令将不会被处理了。
transclude
使用 ng-transclude 要特别注意, 它会自动帮我们创建一个scope,并继承"跨parent scope" .
在做坎套指令时, 比如 ctrlA内有b指令内有c指令 , 在b模板中我们ng-transclude了c , 那么c的$scope会自动被创建新的并继承A, 而不是B哦!B-C是sibling $scope 的关系 .
这样的好处是如果指令B是一个隔离scope,那么我们的C指令还是有办法可以访问到A$scope内容。
这种情况只有ng-transclude可以做到,那么如果我们不希望这样,而是要C继承B的话,那么我们可以自己写一个my-transclude.在link中调用transludeFn来实现即可
transclude : true | "element"
先说true的情况
<myelem2> <div>123</div> <%--这一段,指令元素的子层会被cut进去模板中--%> </myelem2> directive("myelem2", function () { return { restrict: "E", link: function (scope, element, attrs, controllers) { alert("myelem2"); }, transclude: true, template: '<div ng-transclude></div>', //模板中其中一个元素放入指令 ng-transclude,外边的子层就会被cut进来这里了 replace : true } })
"element" 的意思是把整个指令元素都cut进来,所以如果你的设置是replace= false 的话,就不会有东西出现了。
link: function (scope, iElement, iAttrs, controller, transcludeFn) { //如果是"element",可以调用transcludeFn //可以在这里for loop 调用transcludeFn ,然它clone多多出来 transcludeFn(scope, function (clone) { //传入scope,和方法 clone.CSS('background-color', 'yellow'); //这里的clone是angular帮我们clone出来的阶段 iElement.after(clone); }) alert("myelem2"); },
参考ng-repeat,它也是用link 而不是compile 来做循环clone. 所以也搞不清楚compile适合什么场景使用.
require 和 controller
这2个关系密切,就一起介绍吧。
很多时候我们会用多层指令来做一个插件。
比如 :
<myelem1> <myelem2> <myelem3></myelem3> </myelem2></myelem1>
那么它么之间要互相沟通的话可以使用 require 和 controller 来完成
directive("myelem1", function () { return { restrict: "E", link: function (scope, iElement, iAttrs, controller, transcludeFn) { }, template: '<div ng-transclude></div>', replace: true, transclude: true, scope: true, controller: function ($scope) { this.name = function () { //对外提供方法 alert("name"); } }, //name: "myelem1Ctrl" //如果没有取名,那么会自动用指令名来代替 } }).
在一个指令写上controller 和一个 name (name 不写的话会自动用指令的名字替代)
那么这个指令就可以被它子层和同层(同层包括next prev elem指令)的指令require了
这里的controller 是用依赖注入的,和 link complie 不同哦
一般我们会把这个controller 也当成service 或者一个对外开放的接口来看待。
其它指令通过 controller.someFn 来操作我们的scope
directive("myelem3", function () { return { restrict: "E", link: function (scope, iElement, iAttrs, controllers, transcludeFn) { controllers[0].name(); controllers[1].name(); }, template: '<div>myelem3</div>', replace: true, require: ["?^myelem1","?^myelem2"], //它可以找多层,但是找不到普通的controller,只能找指令的controller }
新闻热点
疑难解答