在介绍directive之前,我想先讲讲MVC这个框架的相关知识。这样可以更好的理解angular。
什么是MVC?mvc是一种设计模式,它把应用划分为三个部分,数据(模型),展现层(视图),控制交互层(控制器),一个时间的发生时这样的过程:
1.用户和应用产生交互
2.控制器的事件处理器被触发
3.控制器从模型中请求数据,并将其交给视图
4.视图将数据呈现给用户。
模型
模型用来存放应用的所有数据对象,模型不必知晓视图和控制器的细节,模型只需包含数据及直接和这些数据相关的逻辑。任何事件处理代码、视图模板,以及那些和模型无关的逻辑都应当隔离在模型之外。将模型和视图的代码混在一起,是违反MVC架构原则的。模型是最应该从你的应用中解耦出来的部分。
当控制器从服务端抓取数据或者创建新的记录时,它就将数据包装成模型实例,也就是说,我们的数据是面向对象的,任何定义在这个数据模型上的函数或者逻辑都可以直接被调用。
因此,不要这样做:
var user = users["foo"]; destroyUser(user);
上面的代码没有命名空间的概念,并且不是面向对象的。如果在应用中定义了另一个destoryUser()函数的话,两个函数就会产生冲突。我们应当确保全局变量和函数的个数尽可能少.而要这样做。
var user = User.find("foo"); user.destroy();
上面的代码中,destory()函数是存放在命名空间User的实例中的。这种代码更加清晰,而且非常容易做继承,类似destory()的这种函数就不用在每个模型中都定义一遍了。
视图
视图层是呈现给用户的,用户与之产生交互,在JavaScript 应用中,视图大都是由HTML、CSS和JavaScript模板组成的。除了模板中简单的条件语句之外,视图不应当包含任何其他逻辑。
这并不是说MVC不允许包含视觉呈现相关的逻辑,只要这部分逻辑没有定义在视图之内即可。我们将视觉呈现逻辑归类为“视图助手”(helper):和视图有关的独立的小型工具函数。
反例——formatDate()函数直接插入视图:
// template.html <div> <script> function formatDate(date) { /* ... */ }; </script> ${ formatDate(this.date) } </div>
应该这样做——所有视觉呈现逻辑都包含在helper变量中,这是一个命名空间,可以防止冲突并保持代码清晰、可扩展:
// helper.js var helper = {}; helper.formatDate = function(){ /* ... */ }; // template.html <div> ${ helper.formatDate(this.date) } </div>
控制器
控制器是模型和视图之间的纽带,控制器从视图获得时间和输入,对它们惊醒处理(很可能包含模型),并相应的跟新视图。当页面加载工作时,控制器会给视图添加事件监听,比如监听表单提交或者按钮点击。然后,当用户和应用产生交互时,控制器中的事件触发器就开始工作了。
下面用jQuery实现一个例子:
var Controller = {}; // 使用匿名函数来封装一个作用域 (Controller.users = function($){ var nameClick = function(){ /* ... */ }; // 在页面加载时绑定事件监听 $(function(){ $("#view .name").click(nameClick); }); })(jQuery);
上面的代码创建了users控制器,这个控制器是放在Controller变量下的命名空间。然后用了一个匿名函数封装了一个作用域,以避免对全局作用域造成污染。当页面加载时,程序给视图元素绑定了点击事件的监听。
上面就是mvc的简单介绍。下面继续directive的属性讲解。
基本上每个directive都会经过$compile编译,然后通过link函数来拓展相应的DOM元素。下面介绍常用的directive定义的对象:
priority
当有多个directive定义在同一个DOM元素上时,有必要指定directive的应用顺序。priority对象通常用数字表示,数字越大的,相应的指定便优先编译,相反该directive对应的link函数便越靠后执行。priority的值默认为是0.
scope(讲到这个,值得提一提$scope对象,还有在controller中使用$scope和this(当在html中使用controller as时可以使用)的区别,感兴趣的话自行搜索)
它的值有三种;true,false(默认),object(对象)。
什么是angular中的scope?
scope(http://code.angularjs.org/1.0.2/docs/api/ng.$rootScope.Scope)是一个指向model的object。也是表达式的的执行上下文(请自行了解执行上下文的概念)。angular中提供了一些常用API:$watch API(http://code.angularjs.org/1.0.2/docs/api/ng.$rootScope.Scope#$watch),用于监测model的变化,$apply API(http://code.angularjs.org/1.0.2/docs/api/ng.$rootScope.Scope#$apply),用于监测angular 定义的对象内外的所有model的变化。
在angular中,子作用域一般是通过原型继承机制继承其父作用域的属性和方法,但是有一个例外:在directive中,使用scope:{...},这种方式创建的作用域是一个独立的(isolate)作用域,它也有父作用域,但父作用域不在其原型链上,不会对父作用域原型继承,这种方式定义作用域通常用于构造可复用的directive组件,因为这样定义的scope,不会直接访问或者时候修改父作用域的属性,不会产生意外的副作用。
如果我们在子作用域中访问一个父作用域中定义的属性,那么程序会首先在子作用域中寻找该属性,没找到在从原型链上的父作用域中寻找,在没找到,再往上一级原型链找。在angular中,作用域原型链的顶端是$rootScope。
在单独的directive中,scope的概念还是比较清晰的。
当scope取值为false时,此时directive没有独立的scope对象,link函数中引用的scope对象为来自于当前节点的默认controller。
当scope取值为true是时,directive拥有独立的scope对象,此scope是由父scope继承而来,可以访问父scope中的所有属性,此时通过javasript原型继承。值得注意的是:当给此scope继承而来的属性名称赋值时,子scope会相应建立一个本地属性,此时改变的是本scope的变量属性,父scope中的属性是不会改变的。
当scope取值为{propertyName:"[email protected]},此时directive拥有一个隔离的scope对象,其实就是一个全新的scope对象,和上面取值的区别就是不能通过原型继承访问父scope中的属性,但是可以通过$parent属性去访问父scope中对象属性的。
下面讲讲当scope取值为{...}时,申明scope对象的引用修饰符的用法:(http://stackoverflow.com/questions/14050195/angularjs-what-is-the-difference-between-and-in-directive-scope)
1.=或者=attr 隔离作用域的属性与父作用域的属性进行双向绑定,任何一方修改都会影响对方,此时指令中的属性取值为controller中对应的$scope上属性的值,这是最常用的方式;
[email protected]或者@attr 此时指令中的属性取值为html中的字面量或者直接量。这样是建立一个local scope property到DOM的property的绑定,因为值总是string类型,故这个值总是返回一个字符串,并且字符串的值永远是从父作用域继承而来的(即只能读取父作用域中属性值,不能修改,属于单向绑定)。如果没有通过@attr指定属性名称,那么本地名称将于DOM的属性一致。例如<widget my-attr=”hello {{name}}”>,widget的scope定义为:{localName:’@myAttr’}。那么,widget scope property的localName会映射出”hello {{name}}"转换后的真实值。name属性值改变后,widget scope的localName属性也会相应地改变(仅仅单向,与上面的”=”不同)。name属性是在父scope读取的(不是组件scope).
3. & or &attr “Isolate”作用域把父作用域的属性包装成一个函数,从而以函数的方式读写父作用域的属性,包装方法是$parse();
controller
这是嵌套directive之间交互的重要属性对象。
套用一个经典定义( what the O‘Reily AngularJS book by the Google Team has to say):Controller - Create a controller which publishes an API for communicating across directives. A good example is Directive to Directive Communication。意即这个controller是用来存放一些可以在各个directive之间的共享的方法。当两个或更多的directive之间需要通信时(即directive A需要用到directive B中的方法M),这时方法M就可以在directive B中的controller对象中来定义这个方法。此时方法M便类似一些公共的API(可以供其他的directive使用。)详情请参考:http://www.cnblogs.com/xing901022/p/4290411.html
这个controller可以注入以下本地变量:
$scope (当前元素作用域),$element(当前元素),$attrs(当前元素的属性对象),$transclude(后面介绍这个变量,感兴趣的自己去谷歌)。
require
这个对象表示需要另外一个directive B(directive之间的通信)并且将会注入directive B所在的controller到linking function中,它的值一般是‘xxxController‘或者‘^xxxController‘,表示这个directive需要使用directive B的controller属性中的API。
restrict
限制directive在html中作用的方式:
‘E‘ element name,表示以元素的形式在html中作用,例如<my-directive></my-directive>
‘A‘ attribute ,表示以属性的方式作用在html中,例如 <div my-directive=‘exp‘></div>
‘C‘ class ,表示以class的形式在html中作用,例如<div class=‘my-directive : exp ;‘></div>
‘M‘ 以注释的方式在html中作用,例如<!-- directive: my-directive exp -->
当然,以上也可以组合使用,表示逻辑and。例如 restrict : ‘EA‘,\
template
模板代替directive的元素的内容(默认),也可以完全代替元素本身(当replace值为true时有效),也可以用来封装directive的元素的内容(当transclude的值为true时有效)。
取值:
- A string. For example
<div red-on-hover>{{delete_str}}</div>
. - A function which takes two arguments
tElement
andtAttrs
(described in thecompile
function api below) and returns a string value.
templateUrl
模板加载地址,异步加载。
replace
取值为true时,模板将会取代directive的元素;取值为false,模板将会取代directive的元素的内容。
transclude
一般情况下取值有三种true,‘element‘,{...},通常用到前两种。
当取值为true时, transclude the content (i.e. the child nodes) of the directive‘s element.举个例子:
比如说你有一个申明transclude :true的directive叫做my-transclude-true,如下:
<div> <my-transclude-true> <span>{{ something }}</span> {{ otherThing }} </my-transclude-true> </div>
当它被编译之后,就会变成如下:
<div> <my-transclude-true> <!-- transcluded --> </my-transclude-true> </div>
my-transclude-true这个directive的内容content(子节点),即‘<span>{{something..‘,将在这个directive中 可用。
再比如说你有一个申明transclude :‘element‘的directive叫做my-transclude-element,如下:
<div> <my-transclude-element> <span>{{ something }}</span> {{ otherThing }} </my-transclude-element> </div>
被编译之后,就会变成如下:
<div> <!-- transcluded --> </div>
这里,它的整个元素包括它的子节点都将在这个directive中可用。当transclude这个属性被申明为‘element‘时,directive中的template属性将会失去作用。
最后一个重要的属性:link
这个属性只有在compile这个属性未定义时才能使用。
一般的语法糖:link : function (scope,iAttrs,iElement,controller){...}
link function的作用:Programmatically modify resulting DOM element instances, add event listeners, and set up data binding.以编程方式修改生成的DOM元素实例,添加事件监听器,设置数据绑定。这是整个directive逻辑放的最多的地方。
下面说说这个函数的参数含义:
scope directive用来注册监听事件registering watches的作用域;
iElement 调用这个directive的实例的元素,比如:angular.module(‘app‘,[]).directive(‘myDirective‘,function(...){template :‘<div data=‘data‘><ul></ul></div>‘,replace:true});这个directive的元素就是‘<div><ul></ul></div>‘,只有在postlink函数中处理元素的子节点‘<ul></ul>‘才是安全的,因为子节点已经被link了。(https://docs.angularjs.org/api/ng/service/$compile #link)
iAttrs 调用这个directive的实例的元素的属性,如上为[data],是一个属性列表集合。
controller:这个diective所需要的controller实例,通常在require这个属性后面已经写明。
以上大概就是平时开发过程中常用到的几个属性。
后面要续写的内容预知:
1.嵌套directive之间的通讯(即directive之间的交互);
2.directive与controller之间的数据传递和通信;
3.controller与controller之间的数据传递和通信。