浅析AngularJS中的指令


Posted in Javascript onMarch 20, 2016

指令(Directives)是所有AngularJS应用最重要的部分。尽管AngularJS已经提供了非常丰富的指令,但还是经常需要创建应用特定的指令。

说到AngularJS,我们首先想到的大概也就是双向数据绑定和指令系统了,这两者也是AngularJS中最为吸引人的地方。双向数据绑定呢,感觉没什么好说的,那么今天我们就来简单的讨论下AngularJS这个框架的指令系统,本人也是初学,查阅了一些资料,要是有一些说的不好的地方,万望指出。

指令作为AngularJS中最为重要的部分,所以这个框架本身也是自带了比较多的的指令,但是在开发中,这些指令通常不能满足我们的需要,所以我们也是需要自定义一些指令的。那么一个AngularJS指令在HTML代码中可以有四种表现形式:

1、作为一个新的HTML元素来使用。

<hello></hello>或者<hello/>

2、作为一个元素的属性来使用

<div hello></div>

3、作为一个元素的类来使用

<div class="hello"></div>

4、作为注释来使用

<!--directive: hello -->

注意这里有一个陷阱,就是在“directive: hello”这个的后面要有一个空格,不然的话是没有效果的,同时推荐注释的方法的还是少用,如果非要追求高大上,那就随性吧。既然指令有以上四种表现形式,那么具体他是怎么来定义的呢?

.directive('hello',function(){
return {
restrict:'AECM',
template:'<button>click me</button>'
}
})

上面就是定义一个指令最简单的代码了,没有之一。在上面的代码中,directive()这个方法定义了一个新的指令,该方法有两个参数,第一个'hello'就是规定指令的名字为hello,第二个参数是返回指令对象的函数。那么在上面的代码中,该函数主要使用了两个属性来定义这个hello指令:

1、restrict[string]这个属性,主要是用来规定指令在HTML代码中可以使用什么表现形式。A代表属性、E代表元素、C代表类、M代表注释。实际情况中我们一般使用AE这两种方式。

2、template[string or function]这个属性,规定了指令被Angular编译和链接(link)后生成的HTML标记,这个属性可以简单到只有一个HTML文本在里面,也可以特别复杂,当该属性的值为function的时候,那么该方法返回的就是代表模板的字符串,同时也可以在里面使用{{}}这个表达式。

template: function () {
return '<button>click me</button>';
}

但是在一般情况下,template这个属性都会被templateUrl取代掉,用它来指向一个外部的文件地址,所以我们通常把模板放在外部的一个HTML文件中,然后使用templateUrl来指向他。

在定义指令的时候,除了以上两个最基础的属性外,我们还会使用到其他的很多属性,那么下面我们就来一一的说下:

1、priority[number]属性,这个属性是来规定自定义的指令的优先级的,当一个DOM元素上面有一个以上的指令的时候,就需要去比较指令的优先级了,优先级高的指令先执行。这个优先级就是用来在执行指令的compile函数前,先排序的,那么关于compile这个函数,我们会在下面仔细的说下。

2、terminal[boolean]属性,该参数用来定义是否停止当前元素上比本指令优先级低的指令,如果值为true,就是正常情况,按照优先级高低的顺序来执行,如果设置为false,就不会执行当前元素上比本指令优先级低的指令。

3、replace[boolean]属性,这个属性用来规定生成的HTML内容是否会替换掉定义此指令的HTML元素。当我们把该属性的值设为true的时候,打开控制台看看你会发现这个指令生成的元素会是这样的:

浅析AngularJS中的指令

当我们设置为false的时候会是这样的:

浅析AngularJS中的指令

4、link[function]属性,在上面的例子中,我们自定义的指令其实没有多大意义,这只是一个最简单的指令,有好多的属性我们都没有为他定义,所以没有多大用途。比如这个link函数,它包括三个参数:scope、element、attrs。这个link函数主要是用来添加对DOM元素的事件监听、监视模型属性变化、以及更新DOM的。它里面三个参数:

一:scope参数,在我们没有为指令定义scope属性的时候,那么他代表的就是父controller的scope。

二:element参数,就是指令的jQLite(jQuery的子集)包装DOM元素。如果你在引入AngularJS之前引入了jQuery,那么这个元素就是jQuery元素,而不是jQLite元素。由于这个元素已经被jQuery/jQLite包装了,所以我们就在进行DOM操作的时候就不需要再使用 $()来进行包装。

三:attrs参数,它包含了该指令所在元素的属性的标准化参数对象。

5、scope[boolean or object]属性,该属性是用来定义指令的scope的范围,默认情况下是false,也就是说该指令继承了父controller的scope,可以随意的使用父controller的scope里的属性,但是这样的话就会污染到父scope里的属性,这样是不可取的。所以我们可以让scope取以下两个值:true和{}。

当为true的时候,表示让Angular给指令创建一个继承于父scope的scope。

var myapp=angular.module('myapp',[])
.controller('myctrl',['$scope', function ($scope) {
$scope.color='red';
}])
.directive('hello', function () {
return{
restrict:'AECM',
replace:true,
template:'<button ng-click="sayhello()" style="background-color: {{color}}">click me</button>',
scope:true,
link: function (scope,elements,attrs) {
elements.bind('click', function () {
elements.css('background-color','blue');
})
}
}
})

这里我们为父scope定义了一个color的属性,并赋值为red,在hello指令的scope属性中,我们给了true,所以angular就为这个指令创建了一个继承于父scope的scope,然后在template属性中,我们用{{}}使用了从父scope中继承过来的color属性,所以按钮会是红色的。

浅析AngularJS中的指令

当为{}的时候,表示创建一个隔离的scope,不会继承父scope的属性。但是在有的时候我们也要需要访问父scope里的属性或者方法,那么我们应该怎么办呢。angular早就为我们想到了这一点,有以下的三个办法可以让我们记性上面的操作:

一:使用@实现单向绑定,如果我们只给scope的这个{}值的话,那么上面代码的按钮的背景色将会是灰色的。,而如果我们需要使用父scope的color属性的时候,我们可以这样写:

scope{
color:'@color'
}
<hello color="{{color}}"></hello>

这里有两点需要注意:1、scope里的属性color代表的是模板{{}}这个表达式里面的color,两者必须一致。2、scope里的属性color的值,也就是@后面的color,表示的是下面的HTML元素里的属性color,所以这两者也必须一致,当这里的属性名和模板里表达式{{}}里面使用的名称相同的话,就可以省略掉@后面的属性名了,然后写成下面的形式。

scope{
color:'@'
}

从指令中scope的值可以看出,指令模板中的表达式{{}}里的color的指向的是当前元素元素的color属性,这个color属性的值就是父scope的属性color的值。父scope把他的color属性值传递给了当前元素的color属性,然后color属性又把值传递给了模板中表达式里的color,这个过程是单向的。

二:使用=实现双向绑定

.directive('hello', function () {
return{
restrict:'AECM',
replace:true,
template:'<button style="background-color: {{color}}">click me</button>',
scope:{
color:'='
},
link: function (scope,elements,attrs) {
elements.bind('click', function () {
elements.css('background-color','blue');
scope.$apply(function () {
scope.color='pink';
})
})
}
}
})
<hello color="color"></hello>
<input type="text" ng-model="color"/>

这里我们给指令的scope中的color属性和父scope中的color属性进行了双向绑定,并且给指令的link函数里,添加了一个单击事件,点击按钮会让按钮的颜色发生变化,并且改变指令scope的color属性的值,再给HTML页面中加了一个input标签,输出或者输入父scope的color属性的值。这里有一个地方需要注意:当前元素的属性名的值不用再加上{{}}这个表达式了,因为这里父scope传递的是一个真实的scope数据模型,而不是简单的字符串,所以这样我们就可以传递简单的字符串、数组、甚至复杂的对象给指令的scope。现在让我们来看看点击这个按钮将会发生什么。

浅析AngularJS中的指令

这里我们能看到,按钮的颜色变成了粉色的,说明点击让指令的scope的color属性发生了变化,从而导致按钮的颜色发生了变化。但是这里不仅仅是按钮发生了变化,注意看,input表单里的值也变成了pink,这就说明父scope的color属性也发生了变化。 另外,再让我们来给input里面输入一个颜色,看看发生什么变化。

浅析AngularJS中的指令

可以看出当我们在表单里输入另外一种颜色的时候,按钮的颜色也发生了变化,这就说明指令的scope的color属性被改变了。综上我们可以发现使用'='实现的是双向绑定。

三:使用&调用父scope里的方法

var myapp=angular.module('myapp',[])
.controller('myctrl',['$scope', function ($scope) {
$scope.color='red';
$scope.sayhello= function () {
alert('hello');
};
}])
.directive('hello', function () {
return{
restrict:'AECM',
replace:true,
template:'<button ng-click="sayhello()" style="background-color: {{color}}">click me</button>',
scope:{
color:'=',
sayhello:'&'
},
link: function (scope,elements,attrs) {
elements.bind('click', function () {
elements.css('background-color','blue');
scope.$apply(function () {
scope.color='pink';
})
})
}
}
})
<hello color="color" sayhello="sayhello()"></hello>
<input type="text" ng-model="color"/>

这里我们也有两个地方需要注意:1、我们不仅需要在模板中使用ng-click指令,绑定上要调用的父scope中的方法,而且要在给当前元素添加一个属性,并且这个属性指向要调用的父scope的方法。2、指令scope的属性sayhello、当前元素的属性sayhello、模板绑定的事件方法名sayhello这三者要一致。那么这样我们就可以点击按钮,弹出一个对话框了。

浅析AngularJS中的指令

6、transclude[boolean]属性,这个属性用来让我们规定指令是否可以包含任意内容

.directive('hello', function () {
return{
restrict:'AECM',
replace:true,
transclude:true,
scope:{},
template:'<div ng-transclude></div>',
}
})
<hello>
hello
<span>{{color}}</span>
</hello>

当他的值为true的时候,这是页面上输出的值。当为false的时候,页面上将会是空白的。这里有一个地方需要注意,就是<span>{{color}}</span>,这里的color是父scope里的color。并不是指令里的scope的color属性。

7、compile[function]参数,该方法有两个参数element,attrs,第一个参数element指指令所在的元素,第二个attrs指元素上赋予的参数的标准化列表。这里我们也有个地方需要注意:compile 函数不能访问 scope,并且必须返回一个 link 函数。但是如果没有设置 compile 函数,你可以正常地配置 link 函数,(有了compile,就不能用link,link函数由compile返回)。

.directive('hello', function () {
return{
restrict:'AECM',
replace:true,
translude:true,
template:'<button ng-click="sayhello()" style="background-color: {{color}}">click me</button>',
scope:{
color:'=',
sayhello:'&'
},
compile: function (element,attrs) {
return function (scope,elements,attrs) {
elements.bind('click', function () {
elements.css('background-color','blue');
scope.$apply(function () {
scope.color='pink';
})
})
};
}
}
})

现在让我们来点击这个按钮

浅析AngularJS中的指令

我们发现,这里点击按钮之后发生的事情和前面用link属性的一样,这其实是没有多少差别的。

其实在大多数的情况下,我们只需要使用 link 函数。这是因为大部分的指令只需要考虑注册事件监听、监视模型、以及更新DOM等,这些都可以在 link 函数中完成。 但是对于像 ng-repeat 之类的指令,需要克隆和重复 DOM 元素多次,在 link 函数执行之前由 compile 函数来完成。那么为什么我们需要两个分开的函数来完成生成过程,为什么不能只使用一个?要回答好这个问题,我们需要理解指令在Angular中是如何被编译的!

8、指令是如何被编译的

当我们的angular应用引导启动的时候,angular将会使用$compile服务遍历DOM元素,在所有的指令都被识别之后,将会调用指令的compile方法,返回一个link函数,然后将这个link函数添加到稍后执行的 link 函数列表中,这个过程被称为编译阶段。像ng-repeat这样的指令,需要被重复克隆很多次,compile函数只在编译阶段被执行一次,并且复制这些模板,但是link 函数会针对每个被复制的实例被执行。所以分开处理,让我们在性能上有一定的提高(这句话有点不太理解,我是从别的地方copy过来的。原文在这里http://blog.jobbole.com/62249/)。

9、controller[string or function]和require[string or string[]]参数,当我们想要允许其他的指令和你的指令发生交互时,我们就需要使用 controller 函数。当另一个指令想要交互时,它需要声明它对你的指令 controller 实例的引用(require)。

.directive('hello', function () {
return{
scope:{},
require:'^he',
compile: function (element,attrs) {
return function (scope,elements,attrs,cntIns) {
cntIns.fn()
};
}
}
})
.directive('he', function () {
return {
restrict:'AE',
scope:{},
controller: function ($scope, $compile, $http) {
this.fn= function () {
alert('hello');
};
}
}
})
<he>
<hello color="color" sayhello="sayhello()"></hello>
</he>

当页面加载完毕之后,会弹出一个对话框。

浅析AngularJS中的指令

好了上面就是我这段时间学习angular,所了解到的指令的知识,就先写到这里了。

Javascript 相关文章推荐
extjs form textfield的隐藏方法
Dec 29 Javascript
Ext grid 添加右击菜单
Nov 26 Javascript
jquery png 透明解决方案(推荐)
Aug 21 Javascript
javascript AOP 实现ajax回调函数使用比较方便
Nov 20 Javascript
javascript 使用 NodeList需要注意的问题
Mar 04 Javascript
js异步加载的三种解决方案
Mar 04 Javascript
第十章之巨幕页头缩略图与警告框组件
Apr 25 Javascript
JavaScript“尽快失败”的原则实例详解
Oct 08 Javascript
Vue2.x中的Render函数详解
May 30 Javascript
JS+HTML+CSS实现轮播效果
Nov 28 Javascript
ES6 系列之 WeakMap的使用示例
Aug 06 Javascript
VUE中setTimeout和setInterval自动销毁案例
Sep 07 Javascript
简述Matlab中size()函数的用法
Mar 20 #Javascript
详解Matlab中 sort 函数用法
Mar 20 #Javascript
Sort()函数的多种用法
Mar 20 #Javascript
简单理解JavaScript中的封装与继承特性
Mar 19 #Javascript
JavaScript的函数式编程基础指南
Mar 19 #Javascript
深入解析JavaScript中函数的Currying柯里化
Mar 19 #Javascript
Linux下为Node.js程序配置MySQL或Oracle数据库的方法
Mar 19 #Javascript
You might like
动画 《Pokemon Sword·Shield》系列WEB动画《薄明之翼》第2话声优阵容公开!
2020/03/06 日漫
PHP个人网站架设连环讲(三)
2006/10/09 PHP
phpMyAdmin2.11.6安装配置方法
2008/08/24 PHP
PHP版 汉字转码的实现详解
2013/06/09 PHP
php实现的网络相册图片防盗链完美破解方法
2015/07/01 PHP
非常不错的一个javascript 类
2006/11/07 Javascript
js/ajax跨越访问-jsonp的原理和实例(javascript和jquery实现代码)
2012/12/27 Javascript
javascript与jquery中跳出循环的区别总结
2013/11/04 Javascript
jQuery的cookie插件实现保存用户登陆信息
2014/04/15 Javascript
EasyUI布局 高度自适应
2016/06/04 Javascript
BooStrap对导航条的改造实践小结
2016/09/21 Javascript
详解JS对象封装的常用方式
2016/12/30 Javascript
javascript内存分配原理实例分析
2017/04/10 Javascript
vue.js数据绑定的方法(单向、双向和一次性绑定)
2017/07/13 Javascript
js实现扫雷小程序的示例代码
2017/09/27 Javascript
深入理解Vue生命周期、手动挂载及挂载子组件
2017/09/27 Javascript
JS字符串去除连续或全部重复字符的实例
2018/03/08 Javascript
HTML+JS实现“代码雨”效果源码(黑客帝国文字下落效果)
2020/03/17 Javascript
vue-autoui自匹配webapi的UI控件的实现
2020/03/20 Javascript
Antd下拉选择,自动匹配功能的实现
2020/10/24 Javascript
微信小程序onShareTimeline()实现分享朋友圈
2021/01/07 Javascript
[45:38]DOTA2上海特级锦标赛主赛事日 - 1 胜者组第一轮#1Liquid VS Alliance第一局
2016/03/02 DOTA
Python处理文本文件中控制字符的方法
2017/02/07 Python
Django中提供的6种缓存方式详解
2019/08/05 Python
使用matlab或python将txt文件转为excel表格
2019/11/01 Python
python django中8000端口被占用的解决
2019/12/17 Python
python实现人工蜂群算法
2020/09/18 Python
如何利用Python matplotlib绘制雷达图
2020/12/21 Python
松下电器美国官方商店:Panasonic美国
2016/10/14 全球购物
shallow copy和deep copy的区别
2016/05/09 面试题
创业计划书的主要内容有哪些
2014/01/29 职场文书
运动会方阵解说词
2014/02/12 职场文书
幼儿园音乐教学反思
2016/02/18 职场文书
Python基础之教你怎么在M1系统上使用pandas
2021/05/08 Python
一文搞懂redux在react中的初步用法
2021/06/09 Javascript
MySQL数据库如何给表设置约束详解
2022/03/13 MySQL