自定义指令学习有段时间了,学了些纸上谈兵的东西,还没有真正的写个指令出来呢。。。所以,随着学习的接近尾声,本篇除了介绍剩余的几个参数外,还将动手结合使用各参数,写个真正能用的指令出来玩玩。
我们在自定义指令(上)中,写了一个简单的<say-hello></say-hello>,能够跟美女打招呼。但是看看人家ng内置的指令,都是这么用的:ng-model=”m”,ng-repeat=”a in array”,不单单是作为属性,还可以赋值给它,与作用域中的一个变量绑定好,内容就可以动态变化了。假如我们的sayHello可以这样用:<say-hello speak=”content”>美女</say-hello>,把要对美女说的话写在一个变量content中,然后只要在controller中修改content的值,页面就可以显示对美女说的不同的话。这样就灵活多了,不至于见了美女只会说一句hello,然后就没有然后了。
为了实现这样的功能,我们需要使用scope参数,下面来介绍一下。
使用scope为指令划分作用域
顾名思义,scope肯定是跟作用域有关的一个参数,它的作用是描述指令与父作用域的关系,这个父作用域是指什么呢?想象一下我们使用指令的场景,页面结构应该是这个样子:
<div ng-controller="testC">
<say-hello speak="content">美女</say-hello>
</div>
外层肯定会有一个controller,而在controller的定义中大体是这个样子:
var app = angular.module('MyApp', [], function(){console.log('here')});
app.controller('testC',function($scope){
$scope.content = '今天天气真好!';
});
所谓sayHello的父作用域就是这个名叫testC的控制器所管辖的范围,指令与父作用域的关系可以有如下取值:
取值
|
说明
|
false
|
默认值。使用父作用域作为自己的作用域
|
true
|
新建一个作用域,该作用域继承父作用域
|
javascript对象
|
与父作用域隔离,并指定可以从父作用域访问的变量
|
乍一看取值为false和true好像没什么区别,因为取值为true时会继承父作用域,即父作用域中的任何变量都可以访问到,效果跟直接使用父作用域差不多。但细细一想还是有区别的,有了自己的作用域后就可以在里面定义自己的东西,与跟父作用域混在一起是有本质上的区别。好比是父亲的钱你想花多少花多少,可你自己挣的钱父亲能花多少就不好说了。你若想看这两个作用域的区别,可以在link函数中打印出来看看,还记得link函数中可以访问到scope吧。
最有用的还是取值为第三种,一个对象,可以用键值来显式的指明要从父作用域中使用属性的方式。当scope值为一个对象时,我们便建立了一个与父层隔离的作用域,不过也不是完全隔离,我们可以手工搭一座桥梁,并放行某些参数。我们要实现对美女说各种话就得靠这个。使用起来像这样:
scope: {
attributeName1: 'BINDING_STRATEGY',
attributeName2: 'BINDING_STRATEGY',...
}
键为属性名称,值为绑定策略。等等!啥叫绑定策略?最讨厌冒新名词却不解释的行为!别急,听我慢慢道来。
先说属性名称吧,你是不是认为这个attributeName1就是父作用域中的某个变量名称?错!其实这个属性名称是指令自己的模板中要使用的一个名称,并不对应父作用域中的变量。好难懂啊。。。可能这么说太不负责任了,稍后的例子中我们来说明。再来看绑定策略,它的取值按照如下的规则:
符号
|
说明
|
举例
|
@
|
传递一个字符串作为属性的值.
|
str : ‘@string’
|
=
|
使用父作用域中的一个属性,绑定数据到指令的属性中.
|
name : ‘=username’
|
&
|
使用父作用域中的一个函数,可以在指令中调用
|
getName : ‘&getUserName’
|
总之就是用符号前缀来说明如何为指令传值。你肯定迫不及待要看例子了,我们结合例子看一下,小二,上栗子~
举例说明
我想要实现上面想像的跟美女多说点话的功能,即我们给sayHello指令加一个属性,通过给属性赋值来动态改变说话的内容。主要代码如下:
app.controller('testC',function($scope){
$scope.content = '今天天气真好!';
});
app.directive('sayHello',function(){
return {
restrict : 'E',
template : '<div>hello,<b ng-transclude></b>,{{cont}}</div>',
replace : true,
transclude : true,
scope : {
cont : '=speak'
}
};
});
然后在模板中,我们如下使用指令:
<div ng-controller="testC">
<say-hello speak="content">美女</say-hello>
</div>
看看运行效果:
美女换句话
执行的流程是这样的:
① 指令被编译的时候会扫描到template中的{{cont}},发现是一个表达式;
② 查找scope中的规则:通过speak与父作用域绑定,方式是传递父作用域中的属性;
③ speak与父作用域中的content属性绑定,找到它的值“今天天气真好!”
④ 将content的值显示在模板中
这样我们说话的内容cont就跟父作用域绑定到了一其,如果动态修改父作用域的content的值,页面上的内容就会跟着改变,正如你点击“换句话”所看到的一样。
这个例子也太小儿科了吧!简单虽简单,但可以让我们理解清楚,为了检验你是不是真的明白了,可以思考一下如何修改指令定义,能让sayHello以如下两种方式使用:
<span say-hello speak="content">美女</span>
<span say-hello="content" >美女</span>
答案我就不说了,简单的很。下面有更重要的事情要做,我们说好了要写一个真正能用的东西来着。接下来就结合所学到的东西来写一个折叠菜单,即点击可展开,再点击一次就收缩回去的菜单,(偷偷告诉你,这个例子其实是从大漠穷秋的书上抄来的~)。
控制器及指令的代码如下:(为了不让文章太长,我后面的代码要折叠起来了,请自行点开)
View
Code
HTML中的代码如下:
<script id="expanderTemp.html" type="text/ng-template">
<div class="mybox">
<div class="mytitle" ng-click="toggleText()">
{{mytitle}}
</div>
<div ng-transclude ng-show="showText"></div>
</div>
</script>
看看运行效果:
还是比较容易看懂的,我只做一点必要的解释。首先我们定义模板的时候使用了ng的一种定义方式<script type=”text/ng-template” id="expanderTemp.html">,在指令中就可以用templateUrl根据这个id来找到模板。指令中的{{mytitle}}表达式由scope参数指定从etitle传递,etitle指向了父作用域中的title。为了实现点击标题能够展开收缩内容,我们把这部分逻辑放在了link函数中,link函数可以访问到指令的作用域,我们定义showText属性来表示内容部分的显隐,定义toggleText函数来进行控制,然后在模板中绑定好。
如果把showText和toggleText定义在controller中,作为$scope的属性呢?显然是不行的,这就是隔离作用域的意义所在,父作用域中的东西除了title之外通通被屏蔽。
上面的例子中,scope参数使用了=号来指定获取属性的类型为父作用域的属性,如果我们想在指令中使用父作用域中的函数,使用&符号即可,是同样的原理。
以上是本人对scope的理解,另外有一篇文章对Angular作用域的解释也比较详细,有兴趣可以参考http://www.angularjs.cn/A09C。
使用controller和require进行指令间通信
使用指令来定义一个ui组件是个不错的想法,首先使用起来方便,只需要一个标签或者属性就可以了,其次是可复用性高,通过controller可以动态控制ui组件的内容,而且拥有双向绑定的能力。当我们想做的组件稍微复杂一点,就不是一个指令可以搞定的了,就需要指令与指令的协作才可以完成,这就需要进行指令间通信。
想一下我们进行模块化开发的时候的原理,一个模块暴露(exports)对外的接口,另外一个模块引用(require)它,便可以使用它所提供的服务了。ng的指令间协作也是这个原理,这也正是自定义指令时controller参数和require参数的作用。
controller参数用于定义指令对外提供的接口,它的写法如下:
controller: function controllerConstructor($scope, $element, $attrs, $transclude)
它是一个构造器函数,将来可以构造出一个实例传给引用它的指令。为什么叫controller(控制器)呢?其实就是告诉引用它的指令,你可以控制我。至于可以控制那些东西呢,就需要在函数体中进行定义了。先看controller可以使用的参数,作用域、节点、节点的属性、节点内容的迁移,这些都可以通过依赖注入被传进来,所以你可以根据需要只写要用的参数。关于如何对外暴露接口,我们在下面的例子来说明。
require参数便是用来指明需要依赖的其他指令,它的值是一个字符串,就是所依赖的指令的名字,这样框架就能按照你指定的名字来从对应的指令上面寻找定义好的controller了。不过还稍稍有点特别的地方,为了让框架寻找的时候更轻松些,我们可以在名字前面加个小小的前缀:^,表示从父节点上寻找,使用起来像这样:require : ‘^directiveName’,如果不加,$compile服务只会从节点本身寻找。另外还可以使用前缀:?,此前缀将告诉$compile服务,如果所需的controller没找到,不要抛出异常。
所需要了解的知识点就这些,接下来是例子时间,依旧是从书上抄来的一个例子,我们要做的是一个手风琴菜单,就是多个折叠菜单并列在一起,此例子用来展示指令间的通信再合适不过。
首先我们需要定义外层的一个结构,起名为accordion,代码如下:
View
Code
需要解释的只有controller中的代码,我们定义了一个折叠菜单数组expanders,并且通过this关键字来对外暴露接口,提供两个方法。gotOpended接受一个selectExpander参数用来修改数组中对应expander的showText属性值,从而实现对各个子菜单的显隐控制。addExpander方法对外提供向expanders数组增加元素的接口,这样在子菜单的指令中,便可以调用它把自身加入到accordion中。
看一下我们的expander需要做怎样的修改呢:
View
Code
首先使用require参数引入所需的accordion指令,添加?^前缀表示从父节点查找并且失败后不抛出异常。然后便可以在link函数中使用已经注入好的accordionController了,调用addExpander方法将自己的作用域作为参数传入,以供accordionController访问其属性。然后在toggleText方法中,除了要把自己的showText修改以外,还要调用accordionController的gotOpended方法通知父层指令把其他菜单给收缩起来。
指令定义好后,我们就可以使用了,使用起来如下:
<accordion>
<expander ng-repeat="expander in expanders" etitle="expander.title">{{expander.text}}</expander>
</accordion>
外层使用了accordion指令,内层使用expander指令,并且在expander上用ng-repeat循环输出子菜单。请注意这里遍历的数组expanders可不是accordion中定义的那个expanders,如果你这么认为了,说明还是对作用域不够了解。此expanders是ng-repeat的值,它是在外层controller中的,所以,在testC中,我们需要添加如下数据:
View
Code
这下就都全乎了,试一下我们的accordion组件是不是可以正常使用了呢:
理解了其中的道理之后,使用起来就可以得心应手了,我也将在以后的实践中尝试编写更加复杂的组件,此小例子就当是抛砖引玉了~
总结
又到了总结时间,到此为止自定义指令的学习就告一段落了,但我相信相关的知识肯定远远不止这些,真正要将指令在项目中用好,还需要理解指令与ng的其他机制如何相互作用,还需更加深入的了解ng的指令机制等。所以学与用的转变还需要实践的检验。
撰写博客使我的学习进度变的异常缓慢,要加油了!
分享到:
相关推荐
在本文中,我们将深入探讨如何在AngularJS框架中创建自定义指令来封装ECharts 2的雷达图。ECharts是一款由百度开发的开源JavaScript图表库,它提供了丰富的可视化图表类型,包括雷达图。AngularJS是一个强大的前端...
在本文中,我们将深入探讨如何在 AngularJS 中创建自定义指令来封装 ECharts 2 的柱状图。ECharts 是一个基于 JavaScript 的数据可视化库,它提供了丰富的图表类型,包括柱状图、折线图、饼图等。AngularJS 是一个...
在这个场景中,我们关注的是如何利用AngularJS的自定义指令来封装ECharts 2的折线图功能。ECharts是一个基于JavaScript的开源可视化库,它提供了丰富的图表类型,包括折线图、柱状图、饼图等,适用于各种数据展示...
自定义指令是AngularJS中非常强大和灵活的一个特性,它允许开发者封装和复用界面行为。接下来,我们将详细解释自定义指令的原理、实现步骤、实现方法以及相关的注意事项。 ### 指令的原理 在AngularJS中,指令本质...
"angularjs-dropdown-multiselect"是一个专门用于创建下拉多选框的AngularJS指令,它使得在Web应用中实现多选下拉菜单变得极其简单。该指令集成了基本的多选、搜索过滤、分组等功能,极大地提高了用户体验。其主要...
AngularJs-UI, AngularJs Pagination Angularjs分页 -- Want to be the best pagination.
在本文中,我们将深入探讨如何在古老的Internet Explorer 8(IE8)中使用AngularJS 1.3构建前端项目。由于AngularJS 1.3原生不再支持IE8,因此需要一些额外的工作来确保兼容性。`angular.js-ie8-builds-master`这个...
"AngularJS--angular-phonecat"是一个经典的入门教程,旨在帮助初学者理解AngularJS的核心概念和工作原理。尽管描述中提到这个项目可能与网上的教程存在一些小差异且没有包含测试部分,但它仍然是学习AngularJS的一...
在本文中,我们将深入探讨基于AngularJS的前端项目——AngularJS-nvd3-directives,以及如何利用这个库来创建交互式的数据可视化应用。NVD3是一个JavaScript库,它基于D3(Data-Driven Documents)库,专为创建可...
4. `directives.js`:可能包含自定义指令,用于扩展AngularJS的功能,例如验证输入或创建复杂的UI组件。 5. `templates` 文件夹:存放各个视图的HTML模板,与ngRoute配置中的路由对应。 6. `styles.css` 或 `...
"前端项目-angularjs-color-picker" 是一个专门为 AngularJS 框架设计的颜色选择器指令,它允许开发者在 AngularJS 应用中集成色彩选择功能,提供用户友好的交互体验。 **AngularJS 简介** AngularJS 是一个由 ...
在"myforce-angularjs-dropdown-multiselect"项目中,"dropdown-multiselect"是一个自定义AngularJS指令。指令是AngularJS中的一个强大工具,允许我们扩展HTML的语义,创建可重用的UI组件。在这个特定的指令中,它...
本教程将通过"angularjs-chapter1-示例.rar"中的实践案例,深入浅出地讲解AngularJS的基本概念和核心特性,帮助初学者快速上手。 1. **AngularJS简介** AngularJS是MVC(Model-View-Controller)架构的实现,它...
angularjs-chapter3-示例.rar
"angularjs指令 下拉框"这个主题聚焦于如何利用AngularJS创建一个自定义的下拉框(Dropdown)指令。下拉框在Web开发中非常常见,常用于选项选择或者导航菜单。 首先,我们需要理解AngularJS中的指令是如何工作的。...
本篇将详细解析"angularjs-chapter4-示例.rar"中所涵盖的第四章核心概念及实战应用。 在AngularJS的第四章中,主要探讨了以下几个关键知识点: 1. **双向数据绑定**:AngularJS的核心特性之一,它实现了视图与模型...
在介绍AngularJS过滤器-filter与ng-repeat的配合使用时,我们首先需要了解这两个指令在AngularJS框架中所扮演的角色。AngularJS是一个开源的前端框架,由Google支持,广泛用于开发动态Web应用程序。它通过引入双向...
五、使用自定义指令 在HTML模板中,我们可以这样使用这两个自定义指令: ```html <input type="text" ng-model="httpUrl" http-validator required> <input type="text" ng-model="number" number-validator ...
- `element` 参数指定了启动AngularJS的DOM元素,默认情况下为`document`。 - `modules` 参数是一个数组,包含了要加载的模块名称。 - `config` 参数是一个配置对象,用于设置AngularJS应用的初始配置。 4. **...
在这个实例中,我们将深入探讨AngularJS的ng-value指令,以及如何在实际应用中使用它。 ng-value指令主要用于绑定输入元素的值,它可以将模型(model)的值直接赋给HTML元素的value属性。这在处理表单数据时特别...