angular 指令系统(二)自定义指令:
AngularJS应用的模块中directive()这个方法是用来定义指令的,
angular.module('myApp', []) .directive('myDirective', function ($timeout, UserDefinedService) { // 指令定义放在这里 });
directive() 方法可以接受两个参数:
1. name(字符串) 指令的名字,
2.(函数) 这个函数返回一个对象,其中定义了指令的全部行为。 $compile服务利用这个方法返回的对象,在DOM调用指令时来构造指令的行为。
angular.application('myApp', []) .directive('myDirective', function() { return { // 通过设置项来定义指令,在这里进行覆写 }; });
指令的工厂函数只会在编译器第一次匹配到这个指令时调用一次。和controller函数类似,我们通过$injetor.invoke来调用指令的工厂函数。
指令的生命周期开始于$compile方法并结束于link方法。
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(...) { ... } } }; });
restrict是一个可选的参数。 指令在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 -->(两头有空格)
这些选项可以单独使用,也可以混合在一起使用:
app.directive('myDirective', function(){ return { restrict: 'EA' // 输入元素或属性 }; }); //上面的配置可以同时用属性或注释的方式来声明指令: 作为一个属性 <div my-directive></div> 或者作为一个元素 <my-directive></my-directive>
属性是用来声明指令最常用的方式,因为它能在包括老版本的IE浏览器在内的所有浏览器中正常工作,并且不需要在文档头部注册新的标签。
priority 优先级
优先级参数可以被设置为一个数值。大多数指令会忽略这个参数,使用默认值0。 ngRepeat是所有内置指令中优先级最高的,它总是在其他指令之前运行。这样设置主要考虑的是性能。
数值越大,优先级越高,优先级高的先被调用, 优先级相同的指令,声明在前面的先被调用,
terminal (布尔型,true false) 是否停止运行当前元素上比本指令优先级低的指令。但同当前指令优先级相同的指令还是会被执行。
使用了terminal参数的例子是ngView和ngIf。 ngIf的优先级略高于ngView,如果ngIf的表达式值为true, ngView就可以被正常执行,但如果ngIf表达式的值为false,由于ngView的优先级较低就不会被执行
template(字符串或函数) 一段HTML文本; 一个可以接受两个参数的函数,参数为tElement和tAttrs,并返回一个代表模板的字符串。
如果模板字符串中含有多个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(字符串或函数) 外部HTML文件路径的字符串; 或者 两个参数的函数,参数为tElement和tAttrs,并返回一个外部HTML文件路径的字符串。
无 论 哪 种 方 式 , 模 板 的 URL 都 将 通 过 AngularJS 内 置 的 安 全 层 , 特 别 是 $getTrustedResourceUrl,这样可以保护模板不会被不信任的源加载。
通过Ajax异步加载大量的模板将严重拖慢一个客户端应用的速度。为了避免延迟,可以在部署应用之前对HTML模板进行缓存。在大多数场景下缓存都是一个非常好的选择,因为AngularJS通过减少请求数量提升了性能。更多关于缓存的内容请查看第28章。
模板加载后, AngularJS会将它默认缓存到$templateCache服务中。在实际生产中,可以提前将模板缓存到一个定义模板的JavaScript文件中,这样就不需要通过XHR来加载模板了。
replace 如果设置了,值必须为true,因为默认值为false。默认值意味着模板会被当作子元素插入到调用此指令的元素内部:
指令作用域:
$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 和 aThirdProperty
从这里开始, DOM中每个指令调用时都可能会:
直接调用相同的作用域对象;
从当前作用域对象继承一个新的作用域对象;
创建一个同当前作用域相隔离的作用域对象。
前两个div是兄弟元素, 可以通过get和set访问$rootScope。第二个div内部的div同样可以通过get和set访问相同的根作用域。
scope参数是可选的,可以被设置为true或一个对象。默认值是false。
当scope设置为true时,会从父作用域继承并创建一个新的作用域对象。
内置指令ng-controller的作用,就是从父级作用域继承并创建一个新的子作用域。它会创建一个新的从父作用域继承而来的子作用域。
隔离作用域:
创建具有隔离作用域的指令需要将scope属性设置为一个空对象{}。如果这样做了,指令的模板就无法访问外部作用域了:
具有隔离作用域的指令最主要的使用场景是创建可复用的组件,组件可以在未知上下文中使用,并且可以避免污染所处的外部作用域或不经意地污染内部作用域
<div ng-controller='MainController'>
Outside myDirective: {{ myProperty }}
<div my-directive ng-init="myProperty = 'wow, this is cool'">
Inside myDirective: {{ myProperty }}
</div>
</div>
var app = angular.module("app",[]);
app.controller('MainController', function($scope) {
}).directive('myDirective', function() {
return {
restrict: 'A',
scope: {},
template: '<div>Inside myDirective {{ myProperty }}</div>' //这里的myProperty就不能访问myProperty了
};
});
绑定策略:
AngularJS提供了几种方法能够将指令内部的隔离作用域,同指令外部的作用域进行数据绑定。
本地作用域属性:使用@符号将本地作用域同DOM属性的值进行绑定。指令内部作用域可以使用外部作用域的变量, @ (or @attr)
双向绑定:通过=可以将本地作用域上的属性同父级作用域上的属性进行双向的数据绑定。 = (or =attr)
父级作用域绑定 通过&符号可以对父级作用域进行绑定,以便在其中运行函数。 & (or &attr)
<div ng-app="app" ng-controller="testcontrol">
<drink test="{{water}}" /></drink>
</div>
var app = angular.module("app",[]);
app.controller('testcontrol',function($scope){
$scope.water = "测试";
})
.directive('drink', function() {
return {
restrict: 'E',
scope:{
test:'@'
},
template: '<div>Inside --myDirective {{test}} </div>'
};
});
transclude是一个可选的参数。如果设置了,其值必须为true,它的默认值是false。
嵌入通常用来创建可复用的组件,典型的例子是模态对话框或导航栏
<div sidebox title="Links">
<ul>
<li>First link</li>
<li>Second link</li>
</ul>
</div>
app.directive('sidebox', function() {
return {
restrict: 'EA',
transclude: true,
scope:{
title:'@'
},
template:"<div class='content'>\
<h2 class='header'>{{ title }}</h2>\
<span class='content' ng-transclude>\ //此时ng-transclude 就代表前面模板的内容
</span>\
</div>"
}
});
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() {
return {
restrict: 'A',
controller:
function($scope, $element, $attrs, $transclude) {
// 控制器逻辑放在这里
}
}
});
1. $scope 与指令元素相关联的当前作用域。
2. $element 当前指令对应的元素。
3. $attrs 由当前元素的属性组成的对象。 <div id="aDiv"class="box"></div> 有如下属性: { id: "aDiv",class: "box" }
4. $transclude 嵌入链接函数会与对应的嵌入作用域进行预绑定。 transclude链接函数 是实际被执行用来克隆元素和操作DOM 的函数
想要通过指令来添加一个超链接标签。可以在控制器内的$transclude函数中实现,
<div link >一个测试</div>
app.directive('link', function() {
return {
restrict: 'EA',
transclude: true,
controller:function($scope, $element, $transclude, $log) {
$transclude(function(clone) { //此时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则定义可复用的行为。
希望将当前指令的API暴露给其他指令使用,可以使用controller参数,否则可以使用link来构造当前指令元素的功能性。如果我们使用了scope.$watch()或者想要与DOM元素做实时的交互,使用链接(link)会是更好的选择
当想要同当前屏幕上的作用域交互时,可以使用被传入到link函数中的scope参数, 在某些情况下,例如使用了嵌入,控制器中的作用域所反映的作用域可能与我们所期望的不一样,这种情况下, $scope对象无法保证可以被正常更新
controllerAs参数用来设置控制器的别名,可以以此为名来发布控制器,并且作用域可以访问controllerAs。这样就可以在视图中引用控制器,甚至无需注入$scope。
这个例子有待证实:
app.directive('myDirective', function() {
return {
restrict: 'A',
template: '<h4>{{ myController.msg }}</h4>',
controllerAs: 'myController',
controller: function() {
this.msg = "Hello World" //就不用$scope , 此时this就代替了$scope
}
};
});
require参数可以被设置为字符串或数组,字符串代表另外一个指令的名字。 (require表示 指令所依赖的 控制器 或者 其他指令)require会将控制器注入到其值所指定的指令中,并作为当前指令的链接函数的第四个参数。
如果不使用^前缀,指令只会在自身的元素上查找控制器。 require: 'ngModel' 指令定义只会查找定义在指令作当前用域中的ng-model=""。
<!-- 指令会在本地作用域查找ng-model -->
<div my-directive ng-model="object"></div>
require参数的值可以用下面的前缀进行修饰,这会改变查找控制器时的行为:
? 如果在当前指令中没有找到所需要的控制器,会将null作为传给link函数的第四个参数。
^ 如果添加了^前缀,指令会在上游的指令链中查找require参数所指定的控制器。
?^ 我们可选择地加载需要的指令并在父指令链中进行查找。
没有前缀 如果没有前缀,指令将会在自身所提供的控制器中进行查找,如果没有找到任何控制器(或具有指定名字的指令)就抛出一个错误。
这两个要看:
指令中使用子作用域 书本 的 第9章 9。2 节
向指令中传递数据 书本 的 第8章 8。2 节