前言:
从移动端刚入门直接转到前端angularJs快一年了,感觉还是在入门阶段,只会使用一些简单的东西,所以平常有时间会按照入门的思路去补习一下angularjs的基础部分,然后整理出来。这一篇的指令基础,也整理了好两天,借鉴了很多网上的文章,自己慢慢地写了一些小demo,基础部分的东西理解是理解了,不过在实际工作中使用的话还是需要再多研究研究。整理出来分享给大家,让像我一样入门的新手也可以有个全面的了解。
概述
angularJS中呢,有很多印象深刻的且方便的内容,其中呢,数据双向绑定和指令算是比较有特色的两个内容了,我们这呢就讲讲指令的学习。
指令,就是用新属性,来扩展HTML,让DOM元素有用特定的行为。就我个人对指令的理解,就是把一些复杂,复用性多的代码或者代码量大的插件之类的封装成一个指令,在前端html页面上使用,这样既简化了页面,优化了代码,也增加了工作效率。
angularJS自己有很多内置指令,一般前缀都是ng-,比如说ng-model,ng-app,ng-show,ng-repeat等等。但这些也没什么好说的,因为这些基本上都是angular已经封装好的,直接使用就可以了。AngularJS内置的指令外,我们还可以创建自定义指令。
自定义指令
指令的属性
首先先简略的看下自定义的一些基本上属性和作用
属性 | 作用 |
---|---|
restrict | 申明标识符在模版中作为元素,属性,类,注释或组合,如何使用 |
priority | 设置模版中相对于其他标识符的执行顺序 |
Template | 指定一个字符串式的内嵌模版,如果你指定了模版是一个URL,那么是不会使用的 |
tempateUrl | 指定URL加载的模版,如果你已经指定了内嵌的模版字符串,那么它不会使用的 |
Replace | 如果为真,替换当前元素,如果是假或未指定,拼接到当前元素 |
Transclude | 移动一个标识符的原始字节带你到一个新模版的位置 |
Scope | 为这个标识符创建一个新的作用域,而不是继承父作用域 |
Controller | 创建一个控制器通过标识符公开通信API |
Require | 当前标识符需要另外一个标识符提供正确的函数功能 |
Link | 通过代码修改目标DOM元素的实例,添加事件监听,建立数据绑定 |
Compile | 通过标识符拷贝编程修改DOM模版 |
指令基础属性
接下来我们简单的说明下各个属性的具体情况。
1.restrict(string)
简单的一句话来解释这个属性是什么意思,就是决定你所写的自定义指令可以以哪几种方式出现在你的代码中。
这个属性的值有四个,也就是代表可以以四种不同的方式来使用。
值 | 样式 | 概述 | 示例 |
---|---|---|---|
E | Element | 作为一个新的HTML元素来使用。 |
|
A | Attribute | 作为一个元素的属性来使用 |
|
C | Class | 作为一个元素的类来使用 |
|
M | Comment | 作为注释来使用 |
|
一般情况,我们常用的是EA,在第四个注释中使用的时候,冒号后面得加一个空格,不然没法用,不过这种注释形式我们用的也比较少。一般情况下,我们在写指令的时候可以不写restrict属性,那样就会默认是A。
2.priority(number)
这个属性是来规定自定义的指令的优先级的,意思就是一个DOM元素上有多个指令的时候,有限处理哪个就看这个值的设置。优先级高的就先执行。默认是0.一般情况都不需要去设置。
3.templates(string or function)/tempateUrl
规定了指令被Angular编译和链接(link)后生成的HTML标记,可以很简单,也可以很复杂,当值是一个方法的时候,方法返回的就是代表模板的字符串,同事也可以在里面使用{{}}表达式。
template: function () {
return '你好';
}
不过,通常情况下呢,template这个属性都会被templateUrl取代掉,用它来指向一个外部的文件地址,所以我们通常把模板放在外部的一个HTML文件中,然后使用templateUrl来指向他。因为我们想要封装成指令的那一块html有时候代码量会很大,或者是比较复杂,那么直接写在这个属性下面需要拆分,会很麻烦,其实可以单独写一个文件,然后在这个属性下面写上地址指向这个文件。
同时templateUrl用于指定将被加载的服务器文件,我们可以预缓存这些模版,减少get请求数,提高性能
4.Replace(boolean)
这个属性用来规定生成的HTML内容是否会替换掉定义此指令的HTML元素。我们简单的写一个指令
var app = angular.module('testapp', []);
app.directive('hello',function(){
return {
restrict : 'E',
replace : true,
template : 'hello angular'
};
});
使用
这个时候,我们的replace设置的是true,
当我们值设置为false时
两者的差别在于,指令部分会不会被模板中的内容所替换。
5.Transclude(boolean)
这个属性用来让我们规定指令是否可以包含任意内容,默认为false,表示不开启,如果设置为true,则开启该属性,当开启后,则可以在模板中通过ng-transclude方式替换指令元素中的内容。
举个例子来看
哎呦我去
页面是这么显示的:
源码里面则变成这样:
如果值为false,那么页面会变成空白的。在我个人看来,这个属性的设置,就是要把指令里面的内容替换掉,移动原始的内容到新模版中,当设置成为true时,标识符会删除原来的内容,并通过ng-transclude标识符使它重新插入到模版中,也可以这么用
哎呦我去
那么页面会显示 hello 哎呦我去。不过这个属性我们平常写简单的指令的时候也不怎么用得着。
指令的编译和链接函数
上面说了一些基本的属性,基本属性在理解上面相对比较简单,设置值也不复杂,但编译和链接函数涉及到的东西就比较多了。我们在基础属性中也只是把一些模板或者html替换成指令,但是实际上的操作都是在编译或者链接功能里面。这两个功能是angular引用和创建实时视图的后面步骤。
angular初始化过程是这样的。
流程 | 内容 |
---|---|
脚本加载 | 加载angular,查找ng-app标识符找到应用绑定 |
编译阶段 | 在这一阶段,angular遍历DOM标志模版中所有注册的标志,对于每个标识符,基于标识符规则(template,replace,transclude等等)改造DOM,然后如果编译函数存在就调用它,结果一个编译的template函数,它会调用所有的标识符搜集的link韩素 |
链接阶段 | 为了让视图动起来,angular为每个标识符运行link函数,link函数通常在DOM或模型上创建监听器,这些监听器让视图和模型始终保持一致 |
因此到了编译阶段,它处理转换了模版,链接阶段,它处理了修改视图中的数据,沿着这些思路,标识符中表一功能和链接功能主要区别就是链接功能转换了模版自身,而连接功能在模型和视图上创建了动态链接,就是在第二阶段,作用域scpoes被附加到了编译过程的link功能上,通过数据绑定,标识符变活了
1.Scope(boolean or object)
该属性是用来定义指令的scope的范围,默认情况下是false,也就是说继承了父控制器的scope,可以随意使用父控制器中的scope里的属性,但是有时候会污染到父scope的属性,所以得另外设置。
获取作用域scope的三种选择
- 标识符DOM元素中已经存在的作用域
- 创建一个继承封闭的控制器作用域的新作用域,以便读取结构树作用域的所有值。
- 独立作用域,从父类中不继承任何属性,当你需要隔离这个标识符的操作和父类作用域时,创建可从用的组建来使用这个选项
三种方式 | 设置 |
---|---|
已有作用域 | scope:false(如果没有指定,这就是默认值) |
新作用域 | scope:true |
独立作用域 | scope:{属性名次和绑定风格} |
举个例子吧:首先是scope的值不设置的时候,默认是false,和设置成true的时候,是都可以获取到父作用域的属性的。
显示结果都是
但是如果把scope的值设置成{},表示创建一个隔离的scope,不会继承父scope的属性。那么显示的结果只有hello。但是在有的时候我们也要需要访问父scope里的属性或者方法,我们可以通过标识符属性的键值对父类传递指定的属性给独立作用域
符号 | 意义 |
---|---|
@ | 传递字符串属性,你可以通过使用改写{{}}属性值从作用域中进行数据绑定(单向绑定) |
= | 数据绑定属性在标识符父作用域的属性中 |
& | 传递一个来自父作用域的函数,稍后调用 |
- @:如果父作用域的属性内容修改了,子作用域对应的属性内容也会随之修改,而如果子作用域属性内容修改了,是不会影响父作用域对应的属性内容的。
举个例子
那么结果显示是
- =:创建一个父作用域与子作用域可以同时共享的属性,即父作用域修改了该属性,子作用域也随之改变,反之亦然。
举个例子
{{name}}
那么结果显示是
- &:可以在独立的子作用域中直接调用父作用域的方法,在调用时可以向函数传递参数。
举个例子
{{name}}
鼠标移入变红色,移除变黑色,页面显示是这样的(动态图上看不到鼠标):
3.controller(string or function)和require(String or Array)
scope是指令与外界作用域通讯的桥梁,而require是指令与指令之间通讯的桥梁。这个参数最大的作用在于,当要开发单指令无法完成,需要一些组合型指令的控件或功能,例如日期控件,通过require参数,指令可以获得外部其他指令的控制器,从而达到交换数据、事件分发的目的。require的作用是为了让父子指令或者兄弟指令的controller之间搭建一个桥梁。也就是说父指令里的controller里面的数据能分享给子指令的controller,其中子指令的link第四个参数的值是父指令的controller对象的作用域上下文。
require有两个修饰符号:”?”、”^”
- ? : 如果require没有找到相应的指令避免报错,还能确保程序的正常执行
- ^ : 表示往父级查找
controller属性值是一个构造函数,在创建父元素指令时添加,并可以在函数中创建多个属性或方法。
直接拿网上的一个小例子来说明下。
hello
beautiful
页面显示:
控制台显示:
我们简单的看下,html中首先写的是hello指令,这个指令只写了一个对象,里面有三个数据,name,age,job,值分别是张三,25,和程序员。很简单的指令。然后在hello指令内部,有一个是div,就是页面显示hello的div,可以不看,再看下面的调用的beautiful指令,里面又使用了good指令,good的指令也是很简单的,声明了在他的作用域内,name属性的值是good。同时往父级查找hello指令,没找到也没事。第三个指令是beautiful,内部也是找good指令,link第四个参数的值是父指令的controller对象的作用域上下文。link方法内部打印good的那么值,由上面我们看到的good指令,可以知道这个值就是good。good指令中打印的是hello指令的对象,打印出来的也就是hello指令里的对象。最后使用的时候就得到图上的结果。
感觉上面这一堆废话说了也没啥用,总结出来一句话就是good的控制器跟hello的控制器进行通信,beautiful又可以跟同级的good通信。
小结
以上呢就是整理出来的全部内容,在我看到,比较重要的地方是在作用域那块,因为我们平常如果只是使用一些简单的指令的时候,传参调用数据这些是比较常用的,之前写一些简单的指令的时候总是在纠结模板中数据到底怎么去写,怎么传入等等。别的像link,compie,controller的使用区别的话,细看看网上的一些文章也是比较好理解的。触发的顺序直接的差别,相互之间的关系我都有提到等等,
在看完这些东西之后,可以自己试着写一个简单的指令,可以参照我之前一篇文章里写的时间选择器来自己练习练习,顺路还可以优化下我写的那个粗糙的指令。另外废话一句,大家要是看到有什么问题的可以评论我们一起讨论讨论,因为我也是刚整理出来,对他的了解也并不是很全面,大家可以一起来聊聊。借鉴的一些文章就不贴了,百度下一堆,大家自便。