AngularJs应用现在越来越流行了,谷歌都与微软合作支持AngularJS2.0,这是要逆天了,说明AngularJs将来大势所趋。最近想跳槽,又重新拾起了AngluarJs(之前由于缺少项目应用,一直都是学了就忘记了),也写写复习的知识点,希望这次能让我对AngularJs的理解更深入透彻。
AngularJS指令使我们用来扩展浏览器能力的技术之一。在DOM编译期间,和HTML关联着的指令会被检测到,并且被执行。这使得指令可以为DOM指定行为,或者改变它。本质上就是AngularJS扩展具有自定义功能的HTML元素的途径。
AngularJS有一套完整的、可扩展的、用来帮助web应用开发的指令集,它使得HTML可以转变成“特定领域语言(DSL)”。
在开始自定义指令之前,我们有必要了解一下指令在框架中的执行流程。如下:
1.浏览器得到 HTML 字符串内容,解析得到 DOM 结构。
你需要认清这一点,因为我们的模板必须是可被解析的HTML。这是AngularJS和那些“以字符串为基础而非以DOM元素为基础的”模板系统的区别之处。
2.ng 引入,把 DOM 结构扔给 $compile 函数处理:
① 找出 DOM 结构中有变量占位符
② 匹配找出 DOM 中包含的所有指令引用
③ 把指令关联到 DOM
④ 关联到 DOM 的多个指令优先级排列
⑤ 执行指令中的 compile 函数(改变 DOM 结构,返回 link 函数)
⑥ 得到的所有 link 函数组成一个列表作为 $compile 函数的返回
3. 执行 link 函数(连接模板的 scope)。
通过调用链接函数来将模板与作用域链接起来。这会轮流调用每一个指令的链接函数,让每一个指令都能对DOM注册监听事件,和建立对作用域的的监听。这样最后就形成了作用域的DOM的动态绑定。任何一个作用域的改变都会在DOM上体现出来。
这里注意区别一下$compile和compile,前者是ng内部的编译服务,后者是指令中的编译函数,两者发挥作用的范围不同。compile和link函数息息相关又有所区别。了解执行流程对后面的理解会有帮助。
理解一下编译函数与链接函数:
编译函数 - 编译函数在指令中是很少的, 因为大部分指令都只是为了处理相应的DOM元素实例,而不是改变模板DOM元素。考虑到性能问题,任何指令的实例见能被共享的操作都应该移到编译函数中。
链接函数 - 指令很少不带有链接函数,链接函数可以让指令对相应克隆元素注册事件,还可以将作用域中的内容复制到DOM中。
指令的几种使用方式如下:
作为标签:<my-dir></my-dir>
作为属性:<span my-dir="exp"></span>
作为注释:<!-- directive: my-dir exp -->
作为类名:<span class="my-dir: exp;"></span>
关于选择以哪一种方式指令,建议坚持使用属性方式,因为它有较好的跨浏览器兼容。
关于自定义指令的命名,我们是可以随意命名的。注意,所有内置指令的命名空间都使用ng作为前缀。为了防止命名空间冲突,不要在自定义指令前加ng前缀。另外一个需知道的地方,指令命名时用驼峰规则,使用时用-分割各单词。如:定义myDirective,使用时像这样:<my-directive>。也建议大家写指令时,统一以my-打头做好规范,易于区分。
myModule.directive('namespaceDirectiveName', function factory(injectables) { var directiveDefinitionObject = { restrict: string,//指令的使用方式,包括标签,属性,类,注释 priority: number,//指令执行的优先级 如果有ng-init定义了,则它优先级最高 template: string,//指令使用的模板,用HTML字符串的形式表示 templateUrl: string,//从指定的url地址加载模板 replace: bool,//是否用模板替换当前元素,若为false,则append在当前元素上 transclude: bool,//是否将当前元素的内容转移到模板中 scope: bool or object,//指定指令的作用域 controller: function controllerConstructor($scope, $element, $attrs, $transclude){...},//定义与其他指令进行交互的接口函数 require: string,//指定需要依赖的其他指令 link: function postLink(scope, iElement, iAttrs) {...},//以编程的方式操作DOM,包括添加监听器等 compile: function compile(tElement, tAttrs, transclude){ return: { pre: function preLink(scope, iElement, iAttrs, controller){...}, post: function postLink(scope, iElement, iAttrs, controller){...} } }//编程的方式修改DOM模板的副本,可以返回链接函数 }; return directiveDefinitionObject; });
Directive Definition Object 指令定义对象标准解释:
指令定义对象给编译器提供了生成指令需要的细节。这个对象的属性有:
名称name - 当前作用域的名称,在注册是可选的。
优先级priority - 当一个DOM上有多个指令时,有会需要指定指令执行的顺序。 这个优先级就是用来在执行指令的compile函数前先排序的。高优先级的先执行。 相同优先级的指令顺序没有被指定谁先执行。
终端terminal - 如果被设置为true,那么该指令就会在同一个DOM的指令集和中最后被执行。任何其他“terminal”的指令也仍然会执行,因为同级的指令顺序是没有被定义的。
作用域scope- 如果被定义成:
那么就会为当前指令创建一个新的作用域。如果有多个在同一个DOM上的指令要求创建新作用域,那么只有一个新的会被创建。 这一创建新作用域的规则不适用于模板的根节点,因为模板的根节点总是会得到一个新的作用域。
{} 对象哈希 - 那么一个新的“孤立的”作用域就会被创建。这个“孤立的”作用域区别于一般作用域的地方在于,它不会以原型继承的方式直接继承自父作用域。这对于创建可重用的组件是非常有用的,因为可重用的组件一般不应该读或写父作用域的数据。 这个“孤立的”作用域使用一个对象哈希来表示,这个哈希定义了一系列本地作用域属性, 这些本地作用域属性是从父作用域中衍生出来的。这些属性主要用来分析模板的值。这个哈希的键值对是本地属性为键,它的来源为值。
@ 或 @attr - 将本地作用域成员成员和DOM属性绑定。绑定结果总是一个字符串,因为DOM的属性就是字符串。如果DOM属性的名字没有被指定,那么就和本地属性名一样。比如说<widget my-attr="hello {{name}}"> 和作用域对象: { localName:'@myAttr' }。当name值改变的时候, 作用域中的LocalName也会改变。这个name是从父作用域中读来的(而不是组件作用域)。
= 或 =expression(表达式) - 在本地作用域属性和父作用域属性间建立一个双向的绑定。如果没有指定父作用域属性名称,那就和本地名称一样。 比如 <widget my-attr="parentModel"> 和作用域对象: { localModel:'=myAttr' }, 本地属性localModel会反映父作用域中parentModel的值。localModel和parentModel的任一方改变都会影响对方。
& 或 &attr - 提供了一种能在父作用域下执行表达式的方法。如果没有指定父作用域属性名称,那就和本地名称一样。 比如 <widget my-attr="count = count + value">和作用域对象:{ localFn:'increment()' }。本地作用域成员localFn会指向一个increment表达式的函数包装。通常你可以通过这个表达式从本地作用域给父作用域传值, 操作方法是将本地变量名和值得对应关系传给这个表达式的包装函数。比如说,这个表达式是increment(amount),那么你就可以用调用localFn({amount:22})的方式指定amount的值。
控制器controller - 控制器的构造对象。这个控制器函数是在预编译阶段被执行的,并且它是共享的,其他指令可以通过它的名字得到(参考依赖属性[require attribute])。这就使得指令间可以互相交流来扩大自己的能力。会传递给这个函数的参数有:
$scope - 当前元素关联的作用域。
$element - 当前元素
$attrs - 当前元素的属性对象。
$transclude - 模板链接功能前绑定到正确的模板作用域:function(cloneLinkingFn)。
请求require - 请求将另一个控制器作为参数传入到当前链接函数。 这个请求需要传递被请求指令的控制器的名字。如果没有找到,就会触发一个错误。请求的名字可以加上下面两个前缀:
? - 不要触发错误,这只是一个可选的请求。
^ - 没找到的话,在父元素的controller里面也查找有没有。
限制restrict - EACM中的任意一个之母。它是用来限制指令的声明格式的。如果没有这一项。那就只允许使用属性形式的指令。
E - 元素名称:<my-directive></my-directive>
A - 属性: <div my-directive="exp"> </div>
C - 类名:<div class="my-directive: exp;"></div>
M - 注释: <!-- directive: my-directive exp -->
模板template - 将当前的元素替换掉。 这个替换过程会自动将元素的属性和css类名添加到新元素上。更多细节请查考章节“创建widgets”。
模板templateUrl - 和template属性一样,只不过这里指示的是一个模板的URL。因为模板加载是异步的,所有编译和链接都会等到加载完成后再执行。
替换replace - 如果被设置成true那么现在的元素会被模板替换,而不是被插入到元素中。
编译模板transclude - 将元素编译好,使得指令可以开始使用它。一般情况下需要和ngTransclude指令一起使用。 使用嵌入的好处在于链接好书可以获取到预绑定在作用域上的函数。在一个典型的初始化过程中,widget会创建一个孤立的作用域,但是嵌入并不是其中一个子成员,而是这孤立作用域的兄弟成员。这使得widget可以有一个私有的状态,并且嵌入被绑定在父作用于上。
true - 嵌入指令的内容。
'element' - 嵌入整个元素,包括优先级较低的指令。
编译compile - 这就是后面将要讲到的编译函数。
链接link - 这就是后面将要讲到的链接函数。只有没有提供编译函数时才会用到这个值。
指令这块确实知识点比较多,也是angularJS中很重要的一部分。
所有的内置指令的前缀都为ng,不建议自定义指令使用该前缀,以免冲突。
首先从一些常见的内置指令开始。
先列出一些关键的内置指令,顺便简单说说作用域的问题。
ng-model
将表单控件和当前作用域的属性进行绑定,这么解释似乎也不太正确。
ng-init
该指令被调用时会初始化内部作用域。
ng-app
每一次用AngularJS都离不开这个指令,顺便说下$rootScope。
声明了ng-app的元素会成为$rootScope的起点,而$rootScope是作用域链的根,通常声明在<html>你懂的。
也就是说根下的作用域都可以访问它。
但是,不建议过度使用$rootScope,免得全局变量满天飞,效率又差又难管。
ng-controller
我们用这个指令在一个DOM元素上装上controller。
ng-disabled
像这种只要出现则生效的属性,我们可以在AngularJS中通过表达式返回值true/false令其生效。
禁用表单输入字段。
ng-readonly
通过表达式返回值true/false将表单输入字段设为只读
ng-checked
标准的checked属性是一个布尔属性,不需要进行赋值。通过ng-checked将某个表达式同是出现checked属性进行绑定。
ng-select
ng-select可以对是否出现option标签的selected属性进行绑定。
ng-herf
当使用当前作用域中的属性动态创建url时,应该用ng-href代替href
ng-src
angularJs会告诉浏览器呢在ng-src对应的表达式生效之前不要假装图像。
ng-include
使用ng-include可以加载、编译包括外部HTML片段到当前的应用中。
ng中的事件绑定相关指令
ng-change ng-click ng-dbclick ng-mouseenter ng-mouseleave ng-mousemove ng-mouseover ng-mouseup