发表于: 2017-05-29 21:07:43
1 819
今天完成的事情:
ng-book看到第10章,指令。
准备小课堂资料。
明天计划的事情:
接着看看directive封装
遇到的问题:
ng-book上link指令调用factory没那么详细例子,懵逼,但还是学了点其他基础的知识。
收获
10.1 指令定义
对于指令,可以把它简单的理解成在特定DOM元素上运行的函数,指令可以扩展这个元素
的功能
例如, ng-click 可以让一个元素能够监听 click 事件,并在接收到事件的时候执行AngularJS表
达式。正是指令使得AngularJS这个框架变得强大,并且正如所见,我们可以自己创造新的指令。
AngularJS应用的模块中有很多方法可以使用,其中 directive() 这个方法是用来定义指令的:
angular.module('myApp', [])
.directive('myDirective', function ($timeout, UserDefinedService) {
// 指令定义放在这里
});
directive() 方法可以接受两个参数:
1. name (字符串)
指令的名字,用来在视图中引用特定的指令。
2. factory_function (函数)
这个函数返回一个对象,其中定义了指令的全部行为。 $compile 服务利用这个方法返回的对
象,在DOM调用指令时来构造指令的行为。
angular.application('myApp', [])
.directive('myDirective', function() {
// 一个指令定义对象
return {
// 通过设置项来定义指令,在这里进行覆写
};
});
我们也可以返回一个函数代替对象来定义指令,但是像上面的例子一样,通过对象来定义是
最佳的方式。当返回一个函数时,这个函数通常被称作链接传递(postLink)函数,利用它我们
可以定义指令的链接(link)功能。由于返回函数而不是对象会限制定义指令时的自由度,因此
只在构造简单的指令时才比较有用
当AngularJS启动应用时,它会把第一个参数当作一个字符串,并以此字符串为名来注册第
二个参数返回的对象。AngularJS编译器会解析主HTML的DOM中的元素、属性、注释和CSS类名
中使用了这个名字的地方,并在这些地方引用对应的指令。当它找到某个已知的指令时,就会在
页面中插入指令所对应的DOM元素
tip:为了避免与未来的HTML标准冲突,给自定义的指令加入前缀来代表自定义的
命名空间。AngularJS本身已经使用了 ng- 前缀,所以可以选择除此以外的名字。
在例子中我们使用 my- 前缀(比如 my-derictive )。
指令的工厂函数只会在编译器第一次匹配到这个指令时调用一次。和 controller 函数类似,
我们通过 $injetor.invoke 来调用指令的工厂函数。
当AngularJS在DOM中遇到具名的指令时,会去匹配已经注册过的指令,并通过名字在注册
过的对象中查找。此时,就开始了一个指令的生命周期,指令的生命周期开始于 $compile 方法并
结束于 link 方法,在本章后面的内容中我们会详细介绍这个过程。
下面,来看看定义一个指令时可以使用的全部设置选项。
tip:一个JavaScript对象由键和值组成。当一个给定键的值被设置为一个字符串、布
尔值、数字、数组或对象时,我们把这个键称为属性。当把键设置为函数时,
我们把它叫做方法。
可能的选项如下所示,每个键的值说明了可以将这个属性设置为何种类型或者什么样的
函数:
angular.module('myApp', [])
.directive('myDirective', function() {
return {
restrict: String,
priority: Number,
terminal: Boolean,
template: String or Template Function:
function(tElement, tAttrs) (...},
templateUrl: String,
replace: Boolean or String,
scope: Boolean or Object,
transclude: Boolean,
controller: String or
function(scope, element, attrs, transclude, otherInjectables) { ... },
controllerAs: String,
require: String,
link: function(scope, iElement, iAttrs) { ... },
compile: // 返回一个对象或连接函数,如下所示:
function(tElement, tAttrs, transclude) {
return {
pre: function(scope, iElement, iAttrs, controller) { ... },
post: function(scope, iElement, iAttrs, controller) { ... }
}
// 或者
return function postLink(...) { ... }
}
};
});
10.1.1 restrict (字符串)
restrict 是一个可选的参数。它告诉AngularJS这个指令在DOM中可以何种形式被声明。默
认AngularJS认为 restrict 的值是 A ,即以属性的形式来进行声明。
可选值如下:
E(元素)
<my-directive></my-directive>
A(属性,默认值)
<div my-directive="expression"></div>
C(类名)
<div class="my-directive:expression;"></div>
M(注释)
<--directive:my-directive expression-->
这些选项可以单独使用,也可以混合在一起使用:
angular.module('myDirective', function(){
return {
restrict: 'EA' // 输入元素或属性
};
});
上面的配置可以同时用属性或注释的方式来声明指令:
<-- 作为一个属性 -->
<div my-directive></div>
<-- 或者作为一个元素 -->
<my-directive></my-directive>
属性是用来声明指令最常用的方式,因为它能在包括老版本的IE浏览器在内的所有浏览器中
正常工作,并且不需要在文档头部注册新的标签。
tip:尽量避免用注释方式来声明属性。这种方式最初被用来声明由多个标签组成的
指令。这种方法在某些情况下特别有用,比如在 <table> 元素内使用 ng-repeat
指 令 , 但 在 AngularJS 1.2 中 ng-repeat 可 以 通 过 ng-repeat-start 和
ng-repeat-end 来更优雅地满足这个需求,注释模式就没有什么用武之地了。
如果你对此很好奇,可以通过Chrome开发者工具的 element 标签观察一下使用
ng-repeat 时被隐式添加的注释。
元素方式还是属性方式
在页面中通过元素方式创建新的指令可以将一些功能封装在元素内部。例如,如果我们想要
做一个时钟(忽略对老版本IE浏览器的支持),可以创建一个 clock 指令,然后在DOM中用如下
代码来声明:
<my-clock></my-clock>
这样做可以告诉指令的使用者,这里会完整包含应用的某一部分内容。这个时钟并不是对一
个既有时钟的修饰或扩展,而是一个全新的单元。尽管这里也可以使用属性形式声明指令
(AngularJS并不在意),但我们选择了元素形式,因为这样可以更明确地表达意图。
用属性形式来给一个已经存在的元素添加数据或行为。以时钟为例,假设我们更喜欢模拟时钟:
<my-clock clock-display="analog"></my-clock>
如何进行选择,通常取决于定义的指令是否包含某个组件的核心行为,或者用额外的行为、
状态或者其他内容(比如模拟时钟)对某个核心组件进行修饰或扩展。
使用何种指令声明格式的指导原则是能够准确表达每一段代码的意图,创造易于理解和分享
的清晰代码。
另外一个重要的标准,是根据指令是否创建、继承或将自己从所属的环境中隔离出去进行判
断。指令的父子关系对其组成和重用性起着至关重要的作用,会有额外的内容来更加深入地讨论
指令的作用域。
10.1.2 优先级(数值型)
优先级参数可以被设置为一个数值。大多数指令会忽略这个参数,使用默认值0,但也有些
场景设置高优先级是非常重要甚至是必须的。例如, ngRepeat 将这个参数设置为1000,这样就可
以保证在同一元素上,它总是在其他指令之前被调用。
如果一个元素上具有两个优先级相同的指令,声明在前面的那个会被优先调用。如果其中一
个的优先级更高,则不管声明的顺序如何都会被优先调用:具有更高优先级的指令总是优先运行。
tip:ngRepeat 是所有内置指令中优先级最高的,它总是在其他指令之前运行。这样
设置主要考虑的是性能。在讨论编译参数时会更详细介绍性能相关的内容。
10.1.3 terminal (布尔型)
terminal 是一个布尔型参数,可以被设置为 true 或 false 。
这个参数用来告诉AngularJS停止运行当前元素上比本指令优先级低的指令。但同当前指令
优先级相同的指令还是会被执行。
如果元素上某个指令设置了 terminal 参数并具有较高的优先级,就不要再用其他低优先级的
指令对其进行修饰了,因为不会被调用。但是具有相同优先级的指令还是会被继续调用。
使用了 terminal 参数的例子是 ngView 和 ngIf 。 ngIf 的优先级略高于 ngView ,如果 ngIf 的表
达式值为 true , ngView 就可以被正常执行,但如果 ngIf 表达式的值为 false ,由于 ngView 的优先
级较低就不会被执行。
10.1.4 template (字符串或函数)
template 参数是可选的,必须被设置为以下两种形式之一:
q 一段HTML文本;
q 一个可以接受两个参数的函数,参数为 tElement 和 tAttrs ,并返回一个代表模板的字符
串。 tElement 和 tAttrs 中的 t 代表 template ,是相对于 instance 的。在讨论链接和编译
设置时会详细介绍,模板元素或属性与实例元素或属性之间的区别。
AngularJS会同处理HTML一样处理模板字符串。模板中可以通过大括号标记来访问作用域,
例如 {{ expression }} 。
如果模板字符串中含有多个DOM元素,或者只由一个单独的文本节点构成,那它必须被包
含在一个父元素内。换句话说,必须存在一个根DOM元素:
template: '\
<div> <-- single root element -->\
<a href="http://google.com">Click me</a>\
<h1>When using two elements, wrap them in a parent element</h1>\
</div>\
另外,注意每一行末尾的反斜线,这样AngularJS才能正确解析多行字符串。在实际生产中,
更好的选择是使用 templateUrl 参数引用外部模板,因为多行文本阅读和维护起来都是一场噩梦。
模板字符串和 templateURL 中最需要了解的重要功能,是它们如何同作用域链接起来。第8
章讨论了作用域是如何传递给指令的
10.1.5 templateUrl (字符串或函数)
templateUrl 是可选的参数,可以是以下类型:
q 一个代表外部HTML文件路径的字符串;
q 一个可以接受两个参数的函数,参数为 tElement 和 tAttrs ,并返回一个外部HTML文件
路径的字符串。
无论哪种方式,模板的URL都将通过AngularJS内置的安全层,特别是 $getTrusted
ResourceUrl ,这样可以保护模板不会被不信任的源加载。
默认情况下,调用指令时会在后台通过Ajax来请求HTML模板文件。有两件事情需要知道。
q 在本地开发时,需要在后台运行一个本地服务器,用以从文件系统加载HTML模板,否则
会导致Cross Origin Request Script(CORS)错误。
q 模板加载是异步的,意味着编译和链接要暂停,等待模板加载完成。
通过Ajax异步加载大量的模板将严重拖慢一个客户端应用的速度。为了避免延迟,可以在部
署应用之前对HTML模板进行缓存。在大多数场景下缓存都是一个非常好的选择,因为AngularJS
通过减少请求数量提升了性能。更多关于缓存的内容请查看第28章。
模板加载后,AngularJS会将它默认缓存到 $templateCache 服务中。在实际生产中,可以提
前将模板缓存到一个定义模板的JavaScript文件中,这样就不需要通过XHR来加载模板了。
10.1.6 replace (布尔型)
replace 是一个可选参数,如果设置了这个参数,值必须为 true ,因为默认值为 false 。默
认值意味着模板会被当作子元素插入到调用此指令的元素内部:
<div some-directive></div>
.directive('someDirective', function() {
return {
template: '<div>some stuff here<div>'
};
});
调用指令之后的结果如下(这是默认 replace 为 false 时的情况):
<div some-directive>
<div>some stuff here<div>
</div>
如果 replace 被设置为了 true :
.directive('someDirective', function() {
return {
replace: true // 修饰过
template: '<div>some stuff here<div>'
};
});
指令调用后的结果将是:
<div>some stuff here<div>
10.2 指令作用域
为了完全理解指令定义对象中剩下的参数,需要先介绍指令作用域是如何工作的。
$rootScope 这个特殊的对象会在DOM中声明 ng-app 时被创建:
<div ng-app="myApp" ng-init="someProperty = 'some data'"></div>
<div ng-init="siblingProperty = 'more data'">
Inside Div Two
<div ng-init="aThirdProperty"></div>
</div>
上面的代码中,我们在应用的根作用域中设置了三个属性: someProperty 、 siblingProperty
和 anotherSiblingProperty 。
从这里开始,DOM中每个指令调用时都可能会:
q 直接调用相同的作用域对象;
q 从当前作用域对象继承一个新的作用域对象;
q 创建一个同当前作用域相隔离的作用域对象。
上面的列子展示的是第一种情况。前两个 div 是兄弟元素,可以通过 get 和 set 访问 $rootScope 。
第二个 div 内部的 div 同样可以通过 get 和 set 访问相同的根作用域。
指令嵌套并不一定意味着需要改变它的作用域。默认情况下,子指令会被付予访问父DOM
元素对应的作用域的能力,这样做的原因可以通过介绍指令的 scope 参数来理解, scope 参数默
认是 false 。
10.2.1 scope 参数(布尔型或对象)
scope 参数是可选的,可以被设置为 true 或一个对象。默认值是 false 。
当 scope 设置为 true 时,会从父作用域继承并创建一个新的作用域对象。
如果一个元素上有多个指令使用了隔离作用域,其中只有一个可以生效。只有指令模板中的
根元素可以获得一个新的作用域。因此,对于这些对象来说 scope 默认被设置为 true 。
内置指令 ng-controller 的作用,就是从父级作用域继承并创建一个新的子作用域。它会创
建一个新的从父作用域继承而来的子作用域。
用这些新内容更新一下前面的例子:
<div ng-app="myApp" ng-init="someProperty = 'some data'">
<div ng-init="siblingProperty='moredata'">
Inside Div Two: {{ aThirdProperty }}
<div ng-init="aThirdProperty = 'data for 3rd property'" ng-controller="SomeController">
Inside Div Three: {{ aThirdProperty }}
<div ng-init="aFourthProperty">
Inside Div Four: {{ aThirdProperty }}
</div>
</div>
</div>
</div>
如果直接运行这段代码会报错,因为没有在JavaScript中定义所需的控制器,下面就来定义这
个控制器:
angular.module('myApp', [])
.controller('SomeController', function($scope) {
// 可以留空,但需要被定义
})
刷新页面,会发现第二个 div 中由于 {{ aTgirdProperty }} 未定义,因此什么都没有输出。
第三个 div 显示了设置在继承来的作用域中的 data for a 3rd property ,如图10-1所示。
为了进一步证明作用域的继承机制是向下而非向上进行的,下面再看另外一个例子,展示的
是 {{ aThirdProperty }} 从父作用域继承而来:
<div ng-app="myApp" ng-init="someProperty = 'some data'"></div>
<div ng-init="siblingProperty='moredata'">
Inside Div Two: {{ aThirdProperty }}
<div ng-init="aThirdProperty = 'data for 3rd property'" ng-controller="SomeController">
Inside Div Three: {{ aThirdProperty }}
<div ng-controller="SecondController">
Inside Div Four: {{ aThirdProperty }}
</div>
</div>
</div>
在JavaScript中加入 SecondController 的定义:
angular.module('myApp', [])
.controller('SomeController', function($scope) {
// 可以留空,但需要被定义
})
.controller('SecondController', function($scope) {
// 同样可以留空
})
如果要创建一个能够从外部原型继承作用域的指令,将 scope 属性设置为 true :
angular.module('myApp', [])
.directive('myDirective', function() {
return {
restrict: 'A',
scope: true
};
});
下面用指令来改变DOM的作用域:
<div ng-app="myApp" ng-init="someProperty = 'some data'"></div>
<div ng-init="siblingProperty='moredata'">
Inside Div Two: {{ aThirdProperty }}
<div ng-init="aThirdProperty = 'data for 3rd property'" ng-controller="SomeController">
Inside Div Three: {{ aThirdProperty }}
<div ng-controller="SecondController">
Inside Div Four: {{ aThirdProperty }}
<br>
Outside myDirective: {{ myProperty }}
<div my-directive ng-init="myProperty = 'wow, this is cool'">
Inside myDirective: {{ myProperty }}
<div>
</div>
</div>
</div>
在线示例:http://jsbin.com/ITEBAF/1/edit。
现在,我们已经理解了指令的外部作用域和继承作用域是如何工作的,下面介绍最后一个和
作用域相关的难题:隔离作用域。
10.2.2 隔离作用域
隔离作用域可能是 scope 属性三个选项中最难理解的一个,但也是最强大的。隔离作用域的
概念是以面向对象编程为基础的。AngularJS指令的作用域中可以看到如Small Talk语言和SOLID
原则的影子。
具有隔离作用域的指令最主要的使用场景是创建可复用的组件,组件可以在未知上下文中使
用,并且可以避免污染所处的外部作用域或不经意地污染内部作用域。
创建具有隔离作用域的指令需要将 scope 属性设置为一个空对象 {} 。如果这样做了,指令的
模板就无法访问外部作用域了:
<div ng-controller='MainController'>
Outside myDirective: {{ myProperty }}
<div my-directive ng-init="myProperty = 'wow, this is cool'">
Inside myDirective: {{ myProperty }}
</div>
</div>
angular.module('myApp', [])
.controller('MainController', function($scope) {
})
.directive('myDirective', function() {
return {
restrict: 'A',
scope: {},
priority: 100,
template: '<div>Inside myDirective {{ myProperty }}</div>'
};
});
注意,这里为 myDirective 设置了一个高优先级。由于 ngInit 指令会以非零的优先
级运行,这个例子将会优先运行 ngInit 指令,然后才是我们定义的指定,并且这个
myProperty 在 $scope 对象中是有效的。
在线示例:http://jsbin.com/eguDEpa/1/edit。
示例代码的效果与将 scope 设置为 true 几乎是相同的。下面看一下使用继承作用域的指令的
例子,对比一下二者:
<div ng-init="myProperty='wow,thisiscool'">
Surrounding scope: {{ myProperty }}
<div my-inherit-scope-directive></div>
<div my-directive></div>
</div>
JavaScript代码:
angular.module('myApp', [])
.directive('myDirective', function() {
return {
restrict: 'A',
template: 'Inside myDirective, isolate scope: {{ myProperty }}',
scope: {}
};
})
.directive('myInheritScopeDirective', function() {
return {
restrict: 'A',
template: 'Inside myDirective, isolate scope: {{ myProperty }}',
scope: true
};
});
在线示例:http://jsbin.com/OxAlek/1/edit。
理解了最重要的关于作用域的概念后,就可以将隔离作用域中的属性同外部世界进行绑定,
使得隔离作用域可以和外部进行交互。
10.3 绑定策略
使用无数据的隔离作用域并不常见。AngularJS提供了几种方法能够将指令内部的隔离作用
域,同指令外部的作用域进行数据绑定。
为了让新的指令作用域可以访问当前本地作用域中的变量,需要使用下面三种别名中的一种。
本地作用域属性:使用 @ 符号将本地作用域同DOM属性的值进行绑定。指令内部作用域可以
使用外部作用域的变量:
@ (or @attr)
现在,可以在指令中使用绑定的字符串了。
双向绑定:通过 = 可以将本地作用域上的属性同父级作用域上的属性进行双向的数据绑定。
就像普通的数据绑定一样,本地属性会反映出父数据模型中所发生的改变。
= (or =attr)
父级作用域绑定 通过 & 符号可以对父级作用域进行绑定,以便在其中运行函数。意味着对这
个值进行设置时会生成一个指向父级作用域的包装函数。
要使调用带有一个参数的父方法,我们需要传递一个对象,这个对象的键是参数的名称,值
是要传递给参数的内容。
& (or &attr)
例如,假设我们在开发一个电子邮件客户端,并且要创建一个电子邮件的文本输入框:
<input type="text" ng-model="to"/>
<!-- 调用指令 -->
<div scope-example ng-model="to"
on-send="sendMail(email)"
from-name="ari@fullstack.io" />
这里有一个数据模型( ng-model ),一个函数( sendMail() )和一个字符串( from-name )。
在指令中做如下设置以访问这些内容:
scope: {
ngModel: '=', // 将ngModel同指定对象绑定
onSend: '&', // 将引用传递给这个方法
fromName: '@' // 储存与fromName相关联的字符串
}
双向数据绑定
双向数据绑定或许是AngularJS中最强大的功能,借助它我们可以将一个私有作用域中的属
性同DOM中的属性值进行绑定。在前面一章已经有一个很好的例子,展示了 ng-model 如何在外
部世界同自定义的指令之间进行双向数据绑定,这个指令在很多方面都同 ng-bind 相似。回顾一
下上一章的内容,并做相应的练习,来更好地理解这个重要的概念。
10.3.1 transclude
transclude 是一个可选的参数。如果设置了,其值必须为 true ,它的默认值是 false 。
嵌入有时被认为是一个高级主题,但某些情况下它与我们刚刚学习过的作用域之间会有非常
好的配合。使用嵌入也会很好地扩充我们的工具集,特别是在创建可以在团队、项目、AngularJS
社区之间共享的HTML代码片段时。
嵌入通常用来创建可复用的组件,典型的例子是模态对话框或导航栏。
我们可以将整个模板,包括其中的指令通过嵌入全部传入一个指令中。这样做可以将任意内
容和作用域传递给指令。 transclude 参数就是用来实现这个目的的,指令的内部可以访问外部
指令的作用域,并且模板也可以访问外部的作用域对象。
为了将作用域传递进去, scope 参数的值必须通过 {} 或 true 设置成隔离作用域。如果没有设
置 scope 参数,那么指令内部的作用域将被设置为传入模板的作用域。
只有当你希望创建一个可以包含任意内容的指令时,才使用 transclude: true 。
嵌入允许指令的使用者方便地提供自己的HTML模板,其中可以包含独特的状态和行为,并
对指令的各方面进行自定义。
下面一起来实现个小例子,创建一个可以被自定义的可复用指令。
我们来创建一个可以复用的侧边栏,同WordPress博客的侧边栏很相似。我们希望可以保持
CSS样式的一致性,同时又希望可以在复用时尽量少写HTML代码。
例如,假设我们想创建一个包括标题和少量HTML内容的侧边栏,如下所示:
<div sideboxtitle="Links">
<ul>
<li>First link</li>
<li>Second link</li>
</ul>
</div>
为这个侧边栏创建一个简单的指令,并将 transclude 参数设置为 true :
angular.module('myApp', [])
.directive('sidebox', function() {
return {
restrict: 'EA',
scope: {
title: '@'
},
transclude: true,
template: '<div>\
<div>\
<h2>{{ title }}</h2>\
<span ng-transclude>\
</span>\
</div>\
</div>'
};
});
这段代码告诉AngularJS编译器,将它从DOM元素中获取的内容放到它发现 ng-transclude
指令的地方。
借助 transclusion ,我们可以将指令复用到第二个元素上,而无须担心样式和布局的一致
性问题。
例如,下面的代码会产生两个样式完全一致的侧边栏,效果如图10-2所示。
<div sideboxtitle="Links">
<ul>
<li>First link</li>
<li>Second link</li>
</ul>
</div>
<div sideboxtitle="TagCloud">
<div>
<a href="">Graphics</a>
<a href="">AngularJS</a>
<a href="">D3</a>
<a href="">Front-end</a>
<a href="">Startup</a>
</div>
</div>
如果指令使用了 transclude 参数,那么在控制器(下面马上会介绍)中就无法正常监听数
据模型的变化了。这就是最佳实践总是建议在链接函数里使用 $watch 服务的原因。
10.3.2 controller (字符串或函数)
controller 参数可以是一个字符串或一个函数。当设置为字符串时,会以字符串的值为名字,
来查找注册在应用中的控制器的构造函数:
angular.module('myApp', [])
.directive('myDirective', function() {
restrict: 'A', // 始终需要
controller: 'SomeController'
})
// 应用中其他的地方,可以是同一个文件或被index.html包含的另一个文件
angular.module('myApp')
.controller('SomeController', function($scope, $element, $attrs, $transclude) {
// 控制器逻辑放在这里
});
可以在指令内部通过匿名构造函数的方式来定义一个内联的控制器:
angular.module('myApp',[])
.directive('myDirective', function() {
restrict: 'A',
controller:
function($scope, $element, $attrs, $transclude) {
// 控制器逻辑放在这里
}
});
我们可以将任意可以被注入的AngularJS服务传递给控制器。例如,如果我们想要将 $log 服
务传入控制器,只需简单地将它注入到控制器中,便可以在指令中使用它了。
控制器中也有一些特殊的服务可以被注入到指令当中。这些服务有:
1. $scope
与指令元素相关联的当前作用域。
2. $element
当前指令对应的元素。
3. $attrs
由当前元素的属性组成的对象。例如,下面的元素:
<div id="aDiv"class="box"></div>
具有如下的属性对象:
{
id: "aDiv",
class: "box"
}
4. $transclude
嵌入链接函数会与对应的嵌入作用域进行预绑定。
transclude 链接函数是实际被执行用来克隆元素和操作DOM的函数。
在控制器内部操作DOM是和AngularJS风格相悖的做法,但通过链接函数就可以
实现这个需求。仅在 compile 参数中使用 transcludeFn 是推荐的做法。
例如,我们想要通过指令来添加一个超链接标签。可以在控制器内的 $transclude 函数中实
现,如下所示:
angular.module('myApp')
.directive('link', function() {
return {
restrict: 'EA',
transclude: true,
controller:
function($scope, $element, $transclude, $log) {
$transclude(function(clone) {
var a = angular.element('<a>');
a.attr('href', clone.text());
a.text(clone.text());
$log.info("Created new a tag in link directive");
$element.append(a);
});
}
};
});
指令的控制器和 link 函数可以进行互换。控制器主要是用来提供可在指令间复用的行为,但
链接函数只能在当前内部指令中定义行为,且无法在指令间复用。
link 函数可以将指令互相隔离开来,而 controller 则定义可复用的行为。
由于指令可以 require 其他指令所使用的控制器,因此控制器常被用来放置在多个指令间共
享的动作。
如果我们希望将当前指令的API暴露给其他指令使用,可以使用 controller 参数,否则可以
使用 link 来构造当前指令元素的功能性。如果我们使用了 scope.$watch() 或者想要与DOM元素
做实时的交互,使用链接会是更好的选择。
技术上讲, $scope 会在DOM元素被实际渲染之前传入到控制器中。在某些情况下,例如使
用了嵌入,控制器中的作用域所反映的作用域可能与我们所期望的不一样,这种情况下, $scope
对象无法保证可以被正常更新。
当想要同当前屏幕上的作用域交互时,可以使用被传入到 link 函数中的 scope参数。
10.3.3 controllerAs (字符串)
controllerAs 参数用来设置控制器的别名,可以以此为名来发布控制器,并且作用域可以访
问 controllerAs 。这样就可以在视图中引用控制器,甚至无需注入 $scope 。
例如,创建一个 MainController ,然后不要注入 $scope ,如下所示:
angular.module('myApp')
.controller('MainController', function() {
this.name = "Ari";
});
现在,在HTML中无需引用作用域就可以使用 MainController 。
<div ng-appng-controller="MainControllerasmain">
<input type="text" ng-model="main.name" />
<span>{{ main.name }}</span>
</div>
这个参数看起来好像没什么大用,但它给了我们可以在路由和指令中创建匿名控制器的强大
能力。这种能力可以将动态的对象创建成为控制器,并且这个对象是隔离的、易于测试的。
例如,可以在指令中创建匿名控制器,如下所示:
angular.module('myApp')
.directive('myDirective', function() {
return {
restrict: 'A',
template: '<h4>{{ myController.msg }}</h4>',
controllerAs: 'myController',
controller: function() {
this.msg = "Hello World"
}
};
});
10.3.4 require (字符串或数组)
require 参数可以被设置为字符串或数组,字符串代表另外一个指令的名字。 require 会将控
制器注入到其值所指定的指令中,并作为当前指令的链接函数的第四个参数。
字符串或数组元素的值是会在当前指令的作用域中使用的指令名称。
scope 会影响指令作用域的指向,是一个隔离作用域,一个有依赖的作用域或者完全没有作
用域。在任何情况下,AngularJS编译器在查找子控制器时都会参考当前指令的模板。
如果不使用 ^ 前缀,指令只会在自身的元素上查找控制器。
//...
restrict: 'EA',
require: 'ngModel'
//...
指令定义只会查找定义在指令作当前用域中的 ng-model="" 。
<!-- 指令会在本地作用域查找ng-model -->
<div my-directive ng-model="object"></div>
require 参数的值可以用下面的前缀进行修饰,这会改变查找控制器时的行为:
?
如果在当前指令中没有找到所需要的控制器,会将 null 作为传给 link 函数的第四个参数。
^
如果添加了 ^ 前缀,指令会在上游的指令链中查找 require 参数所指定的控制器。
?^
将前面两个选项的行为组合起来,我们可选择地加载需要的指令并在父指令链中进行查找。
没有前缀
如果没有前缀,指令将会在自身所提供的控制器中进行查找,如果没有找到任何控制器(或
具有指定名字的指令)就抛出一个错误。
评论