AngularJS权威指南-指令详解

《AngularJS 权威教程》读书笔记,第十章——指令详解。

指令定义

对于指令,可以把它简单的理解成在特定 DOM 元素上运行的函数,指令可以扩展这个元素的功能!自定义指令语法:

1
2
3
4
5
6
7
angular.module('myApp', [])
.directive('myDirective', function($timeout, UserDefinedService){
// 指令定义放在这里
return {
// 一个指令定义对象,通过设置项来定义指令,在这里进行覆写
};
});

  1. name:指令的名字
  2. factory_function(函数):这个函数返回一个对象,其中定义了指令的全部行为。$compile 服务利用这个方法返回的对象,在 DOM 调用指令时来构造指令的行为。

restrict

restrict 是一个可选的参数,它的值是字符串(E、A、C、M),它告诉 AngularJS 这个指令在 DOM 中可以以何种形式被声明。默认 AngularJS 认为 restrict 的值是 A,即以属性的形式来进行声明

优先级

优先级参数可以被设置为一个数值。大多数指令会忽略这个参数,使用默认值 0。一个元素上具有两个优先级不同的指令,优先级高的指令先执行;一个元素上具有两个优先级相同的指令,声明在前面的那个会被优先调用。

terminal

terminal 是一个布尔型参数,这个参数用来告诉 AngularJS 停止运行当前元素上比本指令优先级低的指令。但同优先级的指令还是会被执行。

template

template参数是可选的,它的值可以是:

  • 字符串:一段 HTML文本(每一行后需要有反斜杠,并可以使用表达式 {{ expression }})
  • 一个接受两个参数的函数,参数为 tElement 和 tAttrs,并返回一个代表模板的字符串。tElement 和 tAttrs 中的 t 代表 template,是相对于 instance 的。在讨论链接和编译设置时会详细介绍,模板元素或属性与实际元素或属性之间的区别

templateUrl

templateUrl是可选的参数,可以是:

  • 一个代表外部 HTML 文件路径的字符串
  • 一个可以接受两个参数的函数,参数为 tElement 和 tAttrs,并返回一个外部 HTML 文件路径的字符串

无论哪种方式,模板的 URL 都将通过 AngularJS 内置的安全层,特别是 $getTrustedResourceUrl,这样可以保护模板不会被不信任的源加载。
默认情况下,调用指令时会在后台通过 Ajax 来请求 HTML 模板文件,所以需要知道两件事情:

  • 在本地开发时,需要在后台运行一个本地服务器,用以从文件系统加载 HTML 模板,否则会导致 Cross Origin Request Script(CORS)错误
  • 模板加载是异步的,意味着编译和链接要暂停,等待模板加载完成

通过 Ajax 异步加载大量模板将严重拖慢一个客户端应用的速度。为了避免延迟,可以在部署应用之前对 HTML 模板进行缓存,关于怎么缓存后面再说。
模板加在后,AngularJS 会将它默认缓存到 $templateCache 服务中。

replace

replace 是可以可选参数,如果设置了这个参数,值必须为 true,因为默认值为 false。默认值意味着模板会被当做子元素插入到调用此指令的元素内部。

指令作用域

scope参数

scope参数是可选的,可以被设置为 true 或一个对象,默认值是 false,表示使用父作用域作为自己的作用域。
当 scope 设置为 true 时,会从父作用域继承并创建一个新的作用域对象。

隔离作用域

创建具有隔离作用域的指令需要将 scope 属性设置为一个空对象 {},这样指令模板就无法访问外部作用域了。

绑定策略

为了在使用了隔离作用域的指令中访问当前本地作用域中变量:

  • 本地作用域属性:@(or @attr),现在可以在指令中使用绑定的字符串了
  • 双向绑定:=(or =attr)
  • 父级作用域绑定:&(or &attr),使用父作用域中的一个函数,可以在指令中调用

transclude

transclude 是一个可选的参数。如果设置了,其值必须是 true,默认值是 false。
transclude 表示是否将当前元素的内容转移到模板中。如果设置为 true ,则表示把使用了指令的元素的内容转移到指令模板中有 ng-transclude 属性的标签内。

controller

controller 参数可以是一个字符串或一个函数。当设置为字符串时,会以字符串的值为名字来查找注册在应用中的控制器的构造函数;设置为函数时,则表示定义一个内联的控制器。控制器可以注入 AngularJS 服务,也可以注入一些特殊服务:

  1. $scope:与指令元素相关联的当前作用域
  2. $element:当前指令对应的元素
  3. $attrs:由当前元素的属性组成的对象
  4. $transclude:嵌入链接函数会与对应的嵌入作用域进行预绑定,transclude 链接函数是实际被执行用来克隆元素和操作 DOM 的函数

指令的控制器和 link 函数可以进行互换。控制器主要是用来提供可在指令间复用的行为,但链接函数只能在当前内部指令中定义行为,且无法在指令间复用,旨在将指令相互隔离开来。
由于指令可以 require 其他指令所使用的控制器,因此控制器常被用来放置在多个指令间共享的动作。如果我们希望将当前指令的 API 暴露给其他指令使用,可以使用 controller 参数,否则可以使用 link 来构造当前指令元素的功能性。
如果我们使用了 scope.$watch() 或者想要与 DOM 元素做实时的交互,使用链接是更好的选择。$scope 会在 DOM 元素被实际渲染之前传入到控制器中。在某些情况下,例如使用了嵌入,控制器中的作用域所反映的作用域可能与我们所期望的不一样,这种情况下,$scope 对象无法保证被正常更新。

controllerAs

controllerAs 参数用来设置控制器的别名,可以以此为名来发布控制器,并且作用域可以访问 controllerAs。这样就可以在视图中引用控制器,甚至无需注入 $scope。

1
2
3
4
5
6
7
8
9
10
11
angular.module('myApp')
.directive('myDirective', function(){
return {
restrict: 'A',
template: '<h4>{{ myController.msg }}</h4>',
controllerAs: 'myController',
controller: function(){
this.msg = 'Hello World'
}
};
});

require

require 参数可以被设置为字符串或数组,字符串代表另外一个指令的名字。require 会将控制器注入到其值所指定的指令中,并作为当前指令的链接函数的第四个参数。
字符串或数组元素的值是会在当前指令的作用域中使用的指令名称。
require 参数的值可以用下面的前缀进行修饰,这会改变查找控制器时的行为:

  1. ?:如果在当前指令中没有找到所需要的控制器,会将 null 作为传递给 link 函数的第四个参数
  2. ^:如果添加了 ^ 前缀,指令会在上游的指令链中查找 require 参数所指定的控制器
  3. ?^:我们可选择地加载需要的指令并在父指令链中进行查找
  4. 没有前缀:指令将会在自身所提供的控制器中进行查找,如果没有找到任何控制器(或具有指定名字的指令)就抛出一个错误

AngularJS的声明周期

在 AngularJS 应用启动前,它们以 HTML 文本的形式保存在文本编辑器中。应用启动后会进行编译和链接,作用域会同 HTML 进行绑定,应用可以对用户在 HTML 中进行的操作进行实时响应。

编译阶段

在编译阶段,AngularJS 会遍历整个 HTML 文档并根据 JavaScript 中的指令定义来处理页面上声明的指令。
一旦对指令和其中的子模板编译完后,会返回一个叫做模板函数的函数。我们有机会在指令的模板函数被返回前,对编译后的 DOM 树进行修改。
一个指令的表现一旦编译完成,马上就可以通过编译函数对其进行访问,编译函数的签名包含有访问指令声明所在的元素(tElement)以及该元素其他属性(tAttrs)的方法。这个编译函数返回前面提到的模板函数,其中含有完整的解析树。
由于每个指令都可以有自己的模板和编译函数,每个模板返回的也都是自己的模板函数。链条顶部的指令会将内部子指令的模板合并在一起成为一个模板函数并返回,但在树的内部,只能通过模板函数访问其所处的分支。
最后,模板函数被传递给编译后的 DOM 树中每个指令定义规则中指定的链接函数。

compile

compile 选项可以返回一个对象或函数。
compile 选项通常不会被频繁使用,但是 link 函数会被经常使用。本质上,我们设置了 link 选项,实际上是创建了一个 postLink() 链接函数,以便 compile() 函数可以定义链接函数。
通常情况下,如果设置了 compile 函数,说明我们希望在指令和实时数据被放到 DOM 中之前进行 DOM 操作,在这个函数中进行诸如添加和删除节点等 DOM 操作是安全的。
compile 和 link 选项是互斥的。如果同时设置了两者,那么会把 compile 所返回的函数当做链接函数,而 link 选项本身则会被忽略。
编译函数负责对模板 DOM 进行转换,链接函数负责将作用域和 DOM 进行链接。

链接

链接函数签名:

1
2
3
4
// require 'SomeController'
link: function(scope, iElement, iAttrs, controller){
// 在这里操作 DOM,可以访问 required 指定的控制器
}

形参含义:

  1. scope:指令用来在其内部注册监听器的作用域
  2. iElement:iElement 参数代表实例元素,指使用此指令的元素
  3. iAttrs:代表实例属性,是一个由定义在元素上的属性组成的标准化列表
  4. controller:指向 require 选项定义的控制器。如果没有设置 require 选项,那么 controller 参数的值为 undefined;如果 require 选项的值有多个,则该参数为数组

ngModel

ngModel 是一个用法特殊的指令,它提供更底层的 API 来处理控制器内的数据。当我们在指令中使用 ngModel 时能够访问一个特殊的 API,这个 API 用来处理数据绑定、验证、CSS 更新等不实际操作 DOM 的事情。