一、directive介绍
1.什么指令,从指令的用法,我可以这么解释,指令使用在dom元素上(例如属性、元素名、注释或class类),可以只是AngularJs的html编译器($compile)将制定的行为附加在该DOM元素上(例如通过事件监听事件),甚至可以转换成DOM元素及其子集。
2.angular 自带了一系列的指令,例如:ng-Bind,ng-model和ng-class。就像创建controller控制器一样,你可以创建自己角色的使用指令。当angular引导你的应用程序时,HTMl编译器会针对Dom元素遍历Dom对应的匹配指令
什么是编译HTML模板
对于angularJS,”编译”表示将指令附加到HTML来使其交互。我们使用术语“编译”的原因附加指令的递归过程反映了编译变成语言中编译源代码的过程。
二、匹配指令
在我们编写指令之前,我们需要知道angular的HTML编译器如何确定何时确定使用给定的指令。
类似于元素匹配选择器时使用的术语,当我们说指令是其声明的一部分时,我们说一个元素匹配了一个指令
例如:
<input ng-model="foo">
//input元素与ng-model指令相匹配
<!DOCTYPE html>
<html lang="en" ng-app>
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div ng-init="apple=1; people=2"></div>
<p>{{apple+people}}</p>
</body>
<script src="../../js/lib/angular.js"></script>
<script>
/*ng 模块常用指令
* 1.ng-app : 表示启动一个angularJs应用 - 通常放在html或body上
* 2.ng-init:表示初始化模型数据 多个变量之间用分号
* 3.ng-bind:表示使用angularJs表达式的值替换当前元素的innerHTML
* 4.ng-controller :表示调用controller函数,实例化一个控制器对象,并决定控制器的作用范围
* 5.ng-repeat ; 用于view中,表示循环功能
* 6.ng-if:用于在view中判断元素是否在DOM树上出现,为true,存在;为false,表示从DOM树上移除
* */
</script>
</html>
三、directive的使用
1.directive定义如下
在主模块app中定义了一个名为hello-world的指令
<div hello-world></div>
angular.module("app",[]).directive("helloWorld",function(){
return{
设置参数来定义指令
}
});
2.指令包含两个部分
.directive(name,function(){
return{
}
})
第一个参数:指令名
第二个参数:对应的工厂函数,返回一个参数值对象。在该对象中,我们设置一些参数,来改变指令的行为。
四、Directive指令内容解读
可大致分为8个内容
1.restrict :字符串(可选参数)
-作用:指明指令在Dom元素中的声明形式
-取值:E(元素) | A(属性) | C(类名)| M(注释)
默认是是A,可以同时包含多个值
-用法:(在Dom元素的展现形式)
E(元素) <directiveName></directiveName>
A(属性) <div directive-name></div>
C(类名) <div class="directive-name"></div>
M(注释) <--directive:directiveName expression-->
2.priority : 数字(可选参数)
-作用:表示若在一个Dom元素上有多个指令,给指令添加该参数值,表示执行各个指令的优先顺序
3.terminal : 布尔值(可选参数)
-作用 : 若该参数设置为true,表示比该指令优先级低的指令不会再执行,优先级相同的还是有效,还是会调用
4.template:字符串或函数(可选参数)
- 作用:表示元素编译此指令时,展示的内容
-用法1:字符串
<!DOCTYPE html>
<html lang="en" ng-app="myApp">
<head>
<meta charset="UTF-8">
<title>directive1</title>
</head>
<body>
<!--1.template-->
<my-directive></my-directive>
</body>
<script src="../../js/lib/angular.js"></script>
<script>
angular.module("myApp",[]).directive("myDirective",function(){
return{
restrict:"ECM",//指令展现形式
replace:false,//是否使用模板替换原始标记(false:不替换)
template:"<div>this is a directive</div>"
}
});
</script>
</html>
-用法2:函数
template:function(tele,tattr){}
该函数接受两个参数
参数1:tele - 该指令所匹配的元素
该例子就是指令展示的形式是元素本身
参数2 : tattr - 该指令绑定元素上所有属性的集合
该例子中title属性是该集合中的成员
执行结果
5.templateUrl :与template相同
值1:一个代表HTML文件路径的字符串
值2:一个函数,可接受两个参数tElement和tAttrs(大致同上)
注意:在本地开发时候,需要运行一个服务器,不然使用templateUrl会报错 Cross Origin Request Script(CORS)错误。由于加载html模板是通过异步加载的,若加载大量的模板会拖慢网站的速度,这里有个技巧,就是先缓存模板
你可以再你的index页面加载好的,将下列代码作为你页面的一部分包含在里面。
<script type="text/ng-tempalte" id="index.html">
<div>hello,我是。。。</div>
</script>
这个id属性值对应的就是templateUrl 参数对应的值
6.replace : 布尔值(字符串)
- 作用:默认值为 false , 当该值为true的时候,表示用模板来替换指令所绑定的Dom元素
7.scope
1)false(默认值),表示指令继承父级的作用域。
2)true ,表示继承父级的作用域的同时,又拥有自己的作用域
3){ } ,表示创建一个自己的隔离作用域
下面来详细了解一下 scope 作用域
案例1:scope:false (继承不隔离)
<!DOCTYPE html>
<html lang="en" ng-app="myApp">
<head>
<meta charset="UTF-8">
<title>directive scope参数机制以及隔离作用域</title>
<link rel="stylesheet" href="../../css/bootstrap.css">
</head>
<body>
<div ng-controller="myController">
<p>父亲:{{name}}
<input type="text" ng-model="name">
</p>
<hello-world></hello-world>
</div>
</body>
<script src="../../js/lib/angular.js"></script>
<script>
angular.module("myApp",[]).controller("myController",function($scope){
//父级作用域
$scope.name = "我是父级";
$scope.age = 12;
$scope.data = {
age:13,
name:"小红"
}
}).directive("helloWorld",function(){
return{
restrict:"EA",
replace:true,
scope:false,
template:'<p>儿子:{{name}} <input type="text" ng-model="name"></p>'
}
});
</script>
</html>
运行结果:
子集中的directive指令自动继承父级的name属性值
当父级的input输入框改变的时候,父级的name属性值发生改变的同时,子集的name属性值也同时变化
当子集改变输入框中的值,name属性值发生变化,父级的name属性也会同时变化
案例2:scope:true(继承隔离)
- 作用:
表示子集directive中若用到了于父级的同名属性,也会调用父级的属性值,
父级的属性值变化,子集的属性也会同时对应发生变化;
但是scope:true,有个特殊的是子集也会拥有自己的作用域,即,当子集的属性值发生变化时,是不会同时反映在父级的同名属性上。
案例3:scope:{} (不继承隔离)
- 作用:表示子集与父级相互独立,互不影响
注意:当你想要创建一个可重用的组件时隔离作用域是一个很好的选择,通过隔离作用域我们确保指令是‘独立’的,并可以轻松地插入到任何HTML app中,并且这种做法防止了父作用域被污染;
对于隔离作用域,可以通过绑定策略与外部发生数据交换,提供了三种方法同隔离之外的地方交互。这三种分别是
1:@ 绑定一个局部 scope 属性到当前 dom 节点的属性值。结果总是一个字符串,因为 dom 属性是字符串。
这里写代码片
2:& 提供一种方式执行一个表达式在父 scope 的上下文中。如果没有指定 attr 名称,则属性名称为相同的本地名称。
这里写代码片
3:= 通过 directive 的 attr 属性的值在局部 scope 的属性和父 scope 属性名之间建立双向绑定。
(案例详见github代码)
这里写代码片
8.require:(字符串或数组)
字符串代表另一个指令的名字,它将会被作为link函数的第四个参数。假设现在我们要编写两个指令,两个指令中的link链接函数中存在有很多重合的方法,这时候我们就可以将这些重复的方法写在第三个指令的controller中(上面也讲到controller经常用来提供指令间的复用行为)然后在这两个指令中,require这个拥有controller字段的的指令(第三个指令),
最后通过link链接函数的第四个参数就可以引用这些重合的方法了。
9.controllerAs:
这个参数的作用是可以设置你的控制器的别名
以前:
controller("myController",["scope",function($scope){
$scope.title = "controller名字";
}])
。。。。
<div ng-app="app" ng-controller="myController">{{title}}</div>
angular 1.2提供了新的语法
this.title="controller的名字"
}])
。。。
<div ng-app="app" ng-controller="myController as demo"></div>
表示名为mycontroller控制器的别名为demo
在指令中我们也可以这么xie
angular.module('myApp',[]).directive("myDirective",function(){
return{
restrict:"ECMA",
transclude:true,
controller:'mycontroller',
controllerAs:'demo'
}
})
10.controller
可以是一个字符串或者函数。
若是为字符串,则将字符串当做是控制器的名字,来查找注册在应用中的控制器的构造函数
这里写代码片
另外还有一些特殊的服务(参数)可以注入
(1)$scope
,与指令元素相关联的作用域
(2)$element
,当前指令对应的 元素
(3)$attrs
,由当前元素的属性组成的对象
(4)$transclude
,嵌入链接函数,实际被执行用来克隆元素和操作DOM的函数
**注意: 除非是用来定义一些可复用的行为,一般不推荐在这使用。
指令的控制器和link函数(后面会讲)可以进行互换。区别在于,控制器主要是用来提供可在指令间复用的行为但link链接函数只能在当前内部指令中定义行为,且无法再指令间复用。**
这里写代码片
$transclude()
在这里,它可以接收两个参数,第一个是$scope,作用域,第二个是带有参数clone的回调函数。而这个clone实际上就是嵌入的内容(经过jquery包装),可以在它上做很多DOM操作。
这里写代码片
注意:使用$transclude
会生成一个新的作用域。
默认情况下,如果我们简单实用$transclude()
,那么默认的其作用域就是$transclude
生成的作用域
但是如果我们实用$transclude($scope,function(clone){})
,那么作用域就是directive的作用域了
那么问题又来了。如果我们想实用父作用域呢
可以使用$scope.$parent
同理想要一个新的作用域也可以使用$scope.$parent.new();
11.require(字符串或数组)
1.字符串代表另一个指令的名字,它将会作为link函数的第四个参数。
2.假设现在我们要编写两个指令,两个指令中的link链接函数中(link函数后面会讲)存在有很多重合的方法,这时候我们就可以将这些重复的方法写在第三个指令的controller中(上面也讲到controller经常用来提供指令间的复用行为)然后在这两个指令中,require这个拥有controller字段的的指令(第三个指令),
3.最后通过link链接函数的第四个参数就可以引用这些重合的方法了。
这里写代码片
注意
上面例子中的指令innerDirective和指令innerDirective2复用了定义在指令outDirective的controller中的方法(say())
也进一步说明了,指令中的controller是用来让不同指令间通信用的。
另外我们可以在require的参数值加上下面的某个前缀,这会改变查找控制器的行为:
(1)没有前缀,指令会在自身提供的控制器中进行查找,如果找不到任何控制器,则会抛出一个error
(2)? - 如果在当前的指令没有找到所需的控制器,则会将null传给link连接函数的第四个参数
(3)^ - 如果在当前的指令没有找到所需的控制器,则会查找父元素的控制器。抛出错误,如果没有找到
(4)^^ - 通过搜索元素的父母找到所需的控制器。抛出错误,如果没有找到。
(5)?^ - 试通过搜索元素及其父母来找到所需的控制器,或者如果找不到则传递 null给link函数的第四个参数。
(6)?^^- 尝试通过搜索元素的父母找到所需的控制器,或者如果没有找到,则传递 null给link连接函数的第四个参数。
Anguar的指令编译过程
1.首先加载angularjs库,查找到ng-app指令,从而找到应用的边界。
2.根据ng-app
划定的作用域来调用$compile
服务进行编译,angularjs会遍历整个HTML文档,并根据js中指令的定义来处理在页面上声明的各个指令按照指令的优先级(priority)排列,根据指令中的配置参数(template,place,transclude等)转换DOM然后就开始按顺序执行各指令的compile函数(如果指令上有定义compile函数)对模板自身进行转换
注意:此处的compile函数是我们指令中配置的,跟上面说的$compile
服务不一样。每个compile函数执行完后都会返回一个link函数,所有的link函数会合成一个大的link函数
然后这个大的link函数就会被执行,主要做数据绑定,通过在DOM上注册监听器来动态修改scope中的数据,或者是使用$watchs监听 scope中的变量来修改DOM,从而建立双向绑定等等。若我们的指令中没有配置compile函数,那我们配置的link函数就会运行,她做的事情大致跟上面complie返回之后所有的link函数合成的的大的link函数差不多。
所以:在指令中compile与link选项是互斥的,如果同时设置了这两个选项,那么就会把compile所返回的函数当做是链接函数,而link选项本身就会被忽略掉