AngularJS 中的指令实践开发指南(一)


Posted in Javascript onMarch 20, 2016

指令(Directives)是所有AngularJS应用最重要的部分。尽管AngularJS已经提供了非常丰富的指令,但还是经常需要创建应用特定的指令。这篇教程会为你讲述如何自定义指令,以及介绍如何在实际项目中使用。在这篇文章的最后(第二部分),我会指导你如何使用Angular指令来创建一个简单的记事本应用。

概述

一个指令用来引入新的HTML语法。指令是DOM元素上的标记,使元素拥有特定的行为。举例来说,静态的HTML不知道如何来创建和展现一个日期选择器控件。让HTML能识别这个语法,我们需要使用指令。指令通过某种方法来创建一个能够支持日期选择的元素。我们会循序渐进地介绍这是如何实现的。 如果你写过AngularJS的应用,那么你一定已经使用过指令,不管你有没有意识到。你肯定已经用过简单的指令,比如 ng-mode, ng-repeat, ng-show等。这些指令都赋予DOM元素特定的行为。例如,ng-repeat 重复特定的元素,ng-show 有条件地显示一个元素。如果你想让一个元素支持拖拽,你也需要创建一个指令来实现它。指令背后基本的想法很简单。它通过对元素绑定事件监听或者改变DOM而使HTML拥有真实的交互性。

jQuery视角

想象一下使用jQuery如何创建一个日期选择器。首先,我们在HTML中添加一个普通的输入框,然后通过jQuery调用 $(element).dataPicker() 来将它转变成一个日期选择器。但是,仔细想一下。当一个设计人员过来检查HTML标记的时候,他/她能否立刻猜到这个字段实际上表示的内容?这只是一个简单的输入框,或者一个日期选择器?你需要查看jQuery代码来确定这些。而Angular的方法是使用一个指令来扩展HTML。所以,一个日期选择器的指令可以是下面的形式:

<input type="text" />

或者是这样:

<input type="text" />

这种创建UI组建的方式更加直接和清晰。你可以轻易地通过查看元素就明白这到底是什么。

创建自定义指令:

一个Angular指令可以有以下的四种表现形式: 1. 一个新的HTML元素(<data-picker></data-picker>) 2. 元素的属性(<input type=”text” data-picker/>) 3. CSS class(<input type=”text” class=”data-picker”/>) 4. 注释(<!?directive:data-picker ?>) 当然,我们可以控制我们的指令在HTML中的表现形式。下面我们来看一下AngularJS中的一个典型的指令的写法。指令注册的方式与 controller 一样,但是它返回的是一个拥有指令配置属性的简单对象(指令定义对象) 。下面的代码是一个简单的 Hello World 指令。

var app = angular.module('myapp', []);
app.directive('helloWorld', function() {
return {
restrict: 'AE',
replace: 'true',
template: '<h3>Hello World!!</h3>'
};
});

在上面的代码中,app.directive()方法在模块中注册了一个新的指令。这个方法的第一个参数是这个指令的名字。第二个参数是一个返回指令定义对象的函数。如果你的指令依赖于其他的对象或者服务,比如 $rootScope, $http, 或者$compile,他们可以在这个时间被注入。这个指令在HTML中以一个元素使用,如下:

<hello-world/>
//OR
<hello:world/>

或者,以一个属性的方式使用:

<div hello-world></div>
//OR
<div hello:world/>

如果你想要符合HTML5的规范,你可以在元素前面添加 x- 或者 data-的前缀。所以下面的标记也会匹配 helloWorld 指令:

<div data-hello-world></div>
//OR
<div x-hello-world></div>

注意: 在匹配指令的时候,Angular会在元素或者属性的名字中剔除 x- 或者 data- 前缀。 然后将 ? 或者 : 连接的字符串转换成驼峰(camelCase)表现形式,然后再与注册过的指令进行匹配。这是为什么,我们在HTML中以 hello-world 的方式使用 helloWorld 指令。其实,这跟HTML对标签和属性不区分大小写有关。 尽管上面的指令仅仅实现了静态文字的显示,但是这里还是有一些有趣的点值得我们去挖掘。我们在指令定义过程中使用了三个属性来配置指令。我们来一一介绍他们的作用。

restrict ? 这个属性用来指定指令在HTML中如何使用(还记得之前说的,指令的四种表示方式吗)。在上面的例子中,我们使用了 ‘AE'。所以这个指令可以被当作新的HTML元素或者属性来使用。如果要允许指令被当作class来使用,我们将 restrict 设置成 ‘AEC'。

template ? 这个属性规定了指令被Angular编译和链接(link)后生成的HTML标记。这个属性值不一定要是简单的字符串。template 可以非常复杂,而且经常包含其他的指令,以及表达式({{ }})等。更多的情况下你可能会见到 templateUrl, 而不是 template。所以,理想情况下,你应该将模板放到一个特定的HTML文件中,然后将 templateUrl 属性指向它。
replace ? 这个属性指明生成的HTML内容是否会替换掉定义此指令的HTML元素。在我们的例子中,我们用 <hello-world></hello-world>的方式使用我们的指令,并且将 replace 设置成 true。所以,在指令被编译之后,生成的模板内容替换掉了 <hello-world></hello-world>。最终的输出是 <h3>Hello World!!</h3>。如果你将 replace 设置成 false,也就是默认值,那么生成的模板会被插入到定义指令的元素中。
打开这个 plunker,在”Hello World!!”右键检查元素内容,来更形象地明白这些。

Link函数和Scope

指令生成出的模板其实没有太多意义,除非它在特定的scope下编译。默认情况下,指令并不会创建新的子scope。更多的,它使用父scope。也就是说,如果指令存在于一个controller下,它就会使用这个controller的scope。 如何运用scope,我们要用到一个叫做 link 的函数。它由指令定义对象中的link属性配置。让我们来改变一下我们的 helloWorld 指令,当用户在一个输入框中输入一种颜色的名称时,Hello World 文字的背景色自动发生变化。同时,当用户在 Hello World 文字上点击时,背景色变回白色。 相应的HTML标记如下:

<body ng-controller="MainCtrl">
<input type="text" ng-model="color" placeholder="Enter a color" />
<hello-world/>
</body>

修改后的 helloWorld 指令如下:

app.directive('helloWorld', function() {
return {
restrict: 'AE',
replace: true,
template: '<p style="background-color:{{color}}">Hello World',
link: function(scope, elem, attrs) {
elem.bind('click', function() {
elem.css('background-color', 'white');
scope.$apply(function() {
scope.color = "white";
});
});
elem.bind('mouseover', function() {
elem.css('cursor', 'pointer');
});
}
};
});

我们注意到指令定义中的 link 函数。 它有三个参数:

scope ? 指令的scope。在我们的例子中,指令的scope就是父controller的scope。
elem ? 指令的jQLite(jQuery的子集)包装DOM元素。如果你在引入AngularJS之前引入了jQuery,那么这个元素就是jQuery元素,而不是jQLite元素。由于这个元素已经被jQuery/jQLite包装了,所以我们就在进行DOM操作的时候就不需要再使用 $()来进行包装。
attr ? 一个包含了指令所在元素的属性的标准化的参数对象。举个例子,你给一个HTML元素添加了一些属性:,那么可以在 link 函数中通过 attrs.someAttribute 来使用它。

link函数主要用来为DOM元素添加事件监听、监视模型属性变化、以及更新DOM。在上面的指令代码片段中,我们添加了两个事件, click,和 mouseover。click 处理函数用来重置 <p> 的背景色,而 mouseover 处理函数改变鼠标为 pointer。在模板中有一个表达式 {{color}},当父scope中的 color 发生变化时,它用来改变 Hello World 文字的背景色。 这个 plunker 演示了这些概念。

compile函数

compile 函数在 link 函数被执行之前用来做一些DOM改造。它接收下面的参数:

tElement ? 指令所在的元素
attrs ? 元素上赋予的参数的标准化列表
要注意的是 compile 函数不能访问 scope,并且必须返回一个 link 函数。但是如果没有设置 compile 函数,你可以正常地配置 link 函数,(有了compile,就不能用link,link函数由compile返回)。compile函数可以写成如下的形式:

app.directive('test', function() {
return {
compile: function(tElem,attrs) {
//do optional DOM transformation here
return function(scope,elem,attrs) {
//linking function here
};
}
};
});

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

指令是如何被编译的

当应用引导启动的时候,Angular开始使用 $compile 服务遍历DOM元素。这个服务基于注册过的指令在标记文本中搜索指令。一旦所有的指令都被识别后,Angular执行他们的 compile 方法。如前面所讲的,compile 方法返回一个 link 函数,被添加到稍后执行的 link 函数列表中。这被称为编译阶段。如果一个指令需要被克隆很多次(比如 ng-repeat),compile函数只在编译阶段被执行一次,复制这些模板,但是link 函数会针对每个被复制的实例被执行。所以分开处理,让我们在性能上有一定的提高。这也说明了为什么在 compile 函数中不能访问到scope对象。 在编译阶段之后,就开始了链接(linking)阶段。在这个阶段,所有收集的 link 函数将被一一执行。指令创造出来的模板会在正确的scope下被解析和处理,然后返回具有事件响应的真实的DOM节点。

改变指令的Scope

默认情况下,指令获取它父节点的controller的scope。但这并不适用于所有情况。如果将父controller的scope暴露给指令,那么他们可以随意地修改 scope 的属性。在某些情况下,你的指令希望能够添加一些仅限内部使用的属性和方法。如果我们在父的scope中添加,会污染父scope。 其实我们还有两种选择:

一个子scope ? 这个scope原型继承子父scope。
一个隔离的scope ? 一个孤立存在不继承自父scope的scope。
这样的scope可以通过指令定义对象中 scope 属性来配置。下面的代码片段是一个例子:

app.directive('helloWorld', function() {
return {
scope: true, 
// use a child scope that inherits from parent
restrict: 'AE',
replace: 'true',
template: '<h3>Hello World!!</h3>'
};
});

上面的代码,让Angular给指令创建一个继承自父socpe的新的子scope。 另外一个选择,隔离的scope:

app.directive('helloWorld', function() {
return {
scope: {}, 
// use a new isolated scope
restrict: 'AE',
replace: 'true',
template: '<h3>Hello World!!</h3>'
};
});

这个指令使用了一个隔离的scope。隔离的scope在我们想要创建可重用的指令的时候是非常有好处的。通过使用隔离的scope,我们能够保证我们的指令是自包含的,可以被很容易的插入到HTML应用中。 它内部不能访问父的scope,所保证了父scope不被污染。 在我们的 helloWorld 指令例子中,如果我们将 scope 设置成 {},那么上面的代码将不会工作。 它会创建一个新的隔离的scope,那么相应的表达式 {{color}} 会指向到这个新的scope中,它的值将是 undefined. 使用隔离的scope并不意味着我们完全不能访问父scope的属性。其实有一些技术可以允许我们访问父scope的属性,甚至监视他们的变化。我们会在指令这个系列的第二部分中讨论这些技术,以及一些更高级的概念,比如 Controller 函数。 第二部分也会和你一起使用Angular指令创建一个较为丰富的记事本应用。

关于AngularJS 中的指令实践指南(一),小编就给大家介绍到这里,下面文章讲给大家介绍angularjs中的指令实践开发指南二(二),希望对大家有所帮助!

Javascript 相关文章推荐
拖动布局之保存布局页面cookies篇
Oct 29 Javascript
早该知道的7个JavaScript技巧
Mar 27 Javascript
js中的前绑定和后绑定详解
Aug 01 Javascript
jQuery 自定义下拉框(DropDown)附源码下载
Jul 22 Javascript
bootstrap模态框实现拖拽效果
Dec 14 Javascript
JavaScript获取ul中li个数的方法
Feb 13 Javascript
基于BootStrap的文本编辑器组件Summernote
Oct 27 Javascript
解决Angular.js中使用Swiper插件不能滑动的问题
Feb 26 Javascript
javascript性能优化之分时函数的介绍
Mar 28 Javascript
新版vue-cli模板下本地开发环境使用node服务器跨域的方法
Apr 03 Javascript
layer弹出子iframe层父子页面传值的实现方法
Nov 22 Javascript
vue点击按钮实现简单页面的切换
Sep 08 Javascript
浅析AngularJS中的指令
Mar 20 #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
You might like
linux使用crontab实现PHP执行计划定时任务
2014/05/10 PHP
通过php修改xml文档内容的方法
2015/01/23 PHP
PHP实现CSV文件的导入和导出类
2015/03/24 PHP
PHP生成随机数的方法总结
2018/03/01 PHP
分享5个非常有用的Laravel Blade指令
2018/05/30 PHP
使用jQuery的将桌面应用程序引入浏览器
2010/11/19 Javascript
jQuery学习笔记之jQuery.fn.init()的参数分析
2014/06/09 Javascript
node.js中的fs.fchownSync方法使用说明
2014/12/16 Javascript
在web中js实现类似excel的表格控件
2016/09/01 Javascript
基于Phantomjs生成PDF的实现方法
2016/11/07 Javascript
nodejs mysql 实现分页的方法
2017/06/06 NodeJs
Angular利用trackBy提升性能的方法
2018/01/26 Javascript
详解vue-admin和后端(flask)分离结合的例子
2018/02/12 Javascript
解决angularjs中同步执行http请求的方法
2018/08/13 Javascript
Vue利用Blob下载原生二进制数组文件
2019/09/25 Javascript
有趣的JavaScript隐式类型转换操作实例分析
2020/05/02 Javascript
2020淘宝618理想生活列车自动领喵币js脚本的代码
2020/06/02 Javascript
在Python3中使用asyncio库进行快速数据抓取的教程
2015/04/02 Python
Python中unittest模块做UT(单元测试)使用实例
2015/06/12 Python
Python基于回溯法子集树模板解决取物搭配问题实例
2017/09/02 Python
Python中的单行、多行、中文注释方法
2018/07/19 Python
django 中QuerySet特性功能详解
2019/07/25 Python
Python操作SQLite/MySQL/LMDB数据库的方法
2019/11/07 Python
布隆过滤器的概述及Python实现方法
2019/12/08 Python
python游戏开发的五个案例分享
2020/03/09 Python
Python基于traceback模块获取异常信息
2020/07/23 Python
HTML5的Video标签有部分MP4无法播放的问题解析(多图)
2017/08/18 HTML / CSS
Too Faced官网:美国知名彩妆品牌
2017/03/07 全球购物
美国时尚在线:Showpo
2017/09/08 全球购物
办公室文秘自我评价
2013/09/21 职场文书
函授大专自我鉴定
2013/11/01 职场文书
护士求职自荐信范文
2014/03/19 职场文书
2014客服代表实习自我鉴定
2014/09/18 职场文书
python实现Thrift服务端的方法
2021/04/20 Python
python中数组和列表的简单实例
2022/03/25 Python
Java8 Stream API 提供了一种高效且易于使用的处理数据的方式
2022/04/13 Java/Android