除了 AngularJS 内置的指令外,我们还可以创建自定义指令。你可以使用 .directive 函数来添加自定义的指令。要调用自定义指令,HTML 元素上需要添加自定义指令名。
使用驼峰法来命名一个指令, runoobDirective, 但在使用它时需要以 - 分割,
内部有许多参数可以供使用。
内部参数
1:restrict
我们自定义的指令可以使用四种方式调用。
而restrict属性就是用来限制其使用的。restrict 值可以是以下几种:
E
作为元素名使用A
作为属性使用C
作为类名使用M
作为注释使用restrict 默认值为 EA
, 即可以通过元素名和属性名来调用指令。
2:priority 该属性用来规定指令的优先级,默认为0。
3:template : 与指令关联的HTML模板,
·4:templateUrl :与指令关联的HTML模板路径,
·5:replace :是否采用HTML模板替换原有的元素
外部属性
1:scope
scope 参数的作用是:隔离指令与所在控制器(父级作用域的控制器)间的作用域,隔离指令与指令之间的作用域。
scope的值是可选的,可选值分别为:false,true,object,默认情况下为false;
·false 共享父作用域
·true 继承父作用域,并且新建独立作用域
·object 不继承父作用域,创建新的独立作用域
.
scope取值为false时,指令和父作用域共同使用用同一个作用域
看如下代码:(在html标签上面加上了ng-app="myApp")
(function() {
angular.module('myApp',[]).controller('myControl',["$scope",function($scope) {
$scope.fatherData='I am father!';
}])
.directive('inputSon' ,[function() {
return {
restrict:'EA',
replace:true,
scope:false,
template:[
'',
''
].join(''),
controller:['$scope',function($scope) {
$scope.fatherData='I am son!';
}]
}
}])
}());
效果截图:我对后面的标签进行修改,那么前面的标签会一起修改,因为他们的作用域相同
scope取true值,那么指令就和父指令作用域的关系类似于原型链继承的方式,先在自己的作用域找,没有就找上一级。
看如下代码:(在html标签上面加上了ng-app="myApp")
(function() {
angular.module('myApp',[]).controller('myControl',["$scope",function($scope) {
$scope.fatherData='I am father!';
}])
.directive('inputSon' ,[function() {
return {
restrict:'EA',
replace:true,
scope:true,
template:[
'',
'',
].join(''),
controller:['$scope',function($scope) {
$scope.fatherData='I am son!';//注销后,变成I am father!
}]
}
}])
}());
当把子指令作用域内的fatherData注释后,那么就变成:
因为在自己的作用域内已经找不到fatherData,那么就去父级找。
scope取{} 时,那么就完全在一个全新的作用域,与父级没有关系(没有继承)
看如下代码:(在html标签上面加上了ng-app="myApp")
(function() {
angular.module('myApp',[]).controller('myControl',["$scope",function($scope) {
$scope.fatherData='I am father!';
}])
.directive('inputSon' ,[function() {
return {
restrict:'EA',
replace:true,
scope:{},
template:[
'',
'',
].join(''),
controller:['$scope',function($scope) {
$scope.fatherData='I am father!';//注释掉后,那么就变成空了
}]
}
}])
}());
如果把子指令作用域内的fatherData注释掉,那么就找不到值变成空
当scope={ //里面有一些东西} (非空对象),指令会将该对象处理成子域scope的扩展属性。这一扩展属性肩负起了指令和父作用域通信的任务。
@ || @attr:单向绑定 子可以感受到父的变化,反之不行 = || =attr:双向绑定 & :从而以函数的方式读写父作用域的属性
使用@时,属性赋值需要用{{}}表达式!!!
使用&时,一定需要用on开头!!!
看如下代码:(在html标签上面加上了ng-app="myApp")
(function() {
angular.module('myApp',[]).controller('myControl',["$scope",function($scope) {
$scope.fatherData='I am father!';
$scope.fatherName='XXX';
$scope.speak=function() {
console.log("汪汪汪");
}
}])
.directive('inputSon' ,[function() {
return {
restrict:'EA',
replace:true,
scope:{
myData:'@',
myName:'=',
onSpeak:'&'
},
template:[
'',
'',
'',
'',
''
].join(''),
controller:['$scope',function($scope) {
}]
}
}])
}());
点击俩个按钮都是有用的,输出"汪汪汪"
现在我们在子级的控制器里面加上一些属性,会影响输出结果吗?
没有变化,与父级是隔离的。
2:require
引入其他指令并注入到控制器中,并作为当前指令的链接函数的第四个参数。require使用字符串或数组元素来传入指令。用通俗的话说require字段,作用是用于指令之间的相互交流。如果是数组,注入的参数是一个相应顺序的数组。如果这样的指令没有被找到,或者该指令没有控制器, 就会报错。 require参数可以加一些前缀:
看如下代码:(在html标签上面加上了ng-app="myApp")
(function() {
angular.module('myApp',[]).controller('myControl',['$scope',function($scope) {
}])
.directive('father',[function() {
return {
scope:{},
replace:true,
restrict:'EA',
template:[
].join(''),
controller:['$scope', function($scope) {
var data=['1','2','3','4'];//定义一个数组
this.click=function() {//定义一个函数,每一次点击就抛出一个数
data.pop();
console.log(data);
}
}]
}
}])
.directive('son',[function() {
return {
scope:{},
replace:true,
restrict:'EA',
template:[
'点一下'
].join(''),
require:'?^father',
link:function($scope,elem,attr,ctrl) {
$scope.sonClick=ctrl.click;//引用那个点击函数,
}
}
}])
}());
假如我们现在需要编写两个指令,在linking函数中有很多重合的方法,为了避免重复,我们可以将这个重复的方法写在第三个指令的 controller中,然后在另外两个需要的指令中require这个拥有controller字段的指令,最后通过linking函数的第四个参数就可以引用这些重合的方法。
link与controller参数都是描述指令行为的参数,但他们两分别负责不同的行为描述。
controller关注的是指令自身内部作用域具备什么样的行为,其关注点在于指令作用域的行为上。
link关注的是指令中HTML模板的操作行为,其关注点在于DOM操作行为上,这个属性只有在compile这个属性未定义时才能使用,.以编程方式修改生成的DOM元素实例,添加事件监听器,设置数据绑定。
link 和 compile (其中link又可以分成pre-link 和post-link)
看如下代码:(在html标签上面加上了ng-app="myApp")
22333
(function() {
angular.module('myApp',[])
.directive('father',[function() {
return {
restrict:'EA',
compile:function(tElem,tAttrs) {
console.log('Fa Complie Now');
return {
pre:function(scope,elem,attr) {
console.log('Fa prelink now');
},
post:function(scope,elem,attr) {
console.log('Fa postlink now');
}
}
}
}
}])
.directive('son',[function() {
return {
restrict:'EA',
compile:function(tElem,tAttrs) {
console.log('Son Complie Now');
return {
pre:function(scope,elem,attr) {
console.log('Son prelink now');
},
post:function(scope,elem,attr) {
console.log('Son postlink now');
}
}
}
}
}])
}())
我们来简单看看那个俩个指令的过程
执行过程非常清楚了,我们看到先进行compile 然后在进行prelink 最后以栈的顺序(后进先出的顺序)执行postlink函数。
compile过程:在编译的阶段,angularJs会遍历整个的文档并根据JavaScript中指令定义来处理页面上什么的指令。在遍历的过程中,有可能一层套着一层,一直延深处遍历。一但遍历和编译完毕就会返回一个叫做模板函数的函数。在这个函数没被返回(return)之前我们可以对编译后的DOM树进行修改。这个原始的dom被称为template element,这个原始的element会被当成参数传给compile函数,在compile函数后,提供一个scope对象,这个对象有可能是父级的,也有可能是自己的,取决于你的scope属性的值。我们对这个原始的dom进行操作是安全的。还记得ng-repeat吗,它增加了dom节点,就是在这个地方操作的。
post-link函数:本质上,当我们设置了link选项,实际上是创建了一个postLink() 链接函数。
刚刚说了是栈的顺序(先进后出)执行postlink函数,也就是说当执行父级的postlink函数时,子级的postlink函数一定都是执行完了的。一般将逻辑写到这里是很安全的。
刚刚说了compile过后,生成了一个scope对象,这个对象在linking阶段是可以用的,已经传到参数列表里面了。
pre-link函数: 在compile之后,但是在post-link之前,可以将这一些逻辑业务写到pre-link函数里面。
注意这里的父级pre-link函数一定在子级pre-link函数之前执行,和上面相反。
//link函数主要用于操作dom元素,给dom元素绑定事件和监听.
link:function($scope,element,attr,ctrl,linker){
//$scope:指令所在的作用域,有关上下文
//element:指令元素的封装,可以调用angular封装的简装jq方法和属性
//attr:指令元素的属性的集合
//ctrl:用于调用其他指令的方法,指令之间的互相通信使用,需要配合require,刚刚演示了
//linker:用于transClude里面嵌入的内容
}
compile:function (tElem,tAttrs) {
//
return {
pre:function(scope,elem,attr) {
//
},
post:function(scope,elem,attr) {
//
}
}
}