一.指令的作用:实现语义化标签
我们常用的HTML标签是这样的:
<div> <span>目录</span> </div>
而使用AngularJS的directive(指令)机制,我们可以实现这样的东西
<tabset> <tab title='Home'> <p>Welcome home!</p> </tab> <tab title='Preferences'> <p>Content</p> </tab> <tabset>
和JSP或者Struts等等框架里面的taglib功能一样,只不过这里是使用JavaScript来实现的。
二.简单实例
DirectiveStudy01.html
<html ng-app='app'> <body> <hello></hello> </body> <script src="lib/angular/angular.js"></script> <script src="directive/HelloDirect.js"></script> </html>
HelloDirect.js
var appModule = angular.module('app', []); appModule.directive('hello', function() { return { restrict: 'E', template: '<div>Hi there</div>', replace: true }; });
运行结果:
对于DirectiveStudy01.html里面的<hello>标签,浏览器显然是不认识的,它唯一能做的事情就是无视这个标签。那么,为了让浏览器能够认识这个标签,我们需要使用Angular来定义一个hello指令(本质上说就是自己来把<hello>替换成浏览器能识别的那些标准HTML标签)。
从运行结果可以看到,<hello>已经被<div>Hi there</div>这个标签替换掉了,这也是以上JS代码里面replace:true这行配置的作用,代码里面的template配置项当然就是我们要的div标签啦,至于restrict:'E'这个配置项的含义,请看下表:
当然,如果需要替换的HTML标签很长,显然不能用拼接字符串的方式来写,这时候我们可以用templateUrl来替代template,从而可以把模板写到一个独立的HTML文件中。
三.transclude(变换)
可以通过transclude属性将内容插入新的模板。当transcluden属性设置为true时,指令会删除原来的内容,使你的模板可以用ng-transclude指令进行重新插入。
DirectiveStudy02.html
<html ng-app='app'> <body> <div hello>Bob</div> </body> <script src="lib/angular/angular.js"></script> <script src="directive/HelloDirect02.js"></script> </html>HelloDirect02.js
var appModule = angular.module('app', []); appModule.directive('hello', function() { return { template: '<div>Hi there <span ng-transclude></span></div>', transclude: true }; });运行结果:
和第一个例子对比,这个例子的JS和HTML代码都略有不同,JS代码里面多了一个transclude: true,HTML代码里面在<hello>内部出现了子标签。
按照我们在第一个例子中的说法,指令的作用是把我们自定义的语义化标签替换成浏览器能够认识的HTML标签。那好,如果我们自定义的标签内部出现了子标签,应该如何去处理呢?很显然,transclude就是用来处理这种情况的。
对于当前这个例子,transclude的作用可以简化地理解成:把<hello>标签替换成我们所编写的HTML模板,但是<hello>标签内部的内容保持不变。
很显然,由于我们没有加replace:true选项,所以<hello>标签还在,没有被替换掉。
四.compile和link函数
虽然插入模板的方式很有用,但是对于指令来说,真正有趣的工作发生在compile和link函数中。
compile和link这两个函数是根据Angular创建动态视图的两个处理阶段来命名的。从高层来看Angular的初始化过程,它们依次如下:
1.加载脚本
加载Angular库,并查找ng-app指令,从而找到应用的边界。
2.编译阶段
在这个阶段,Angular将会遍历DOM结构,标识出模板中注册的所有指令。对于每一条指令,它会根据指令定义的规则(template、replace、transclude等)来转换DOM结构,如果存在compile函数,则调用它。调用compile函数将得到一个编译好的template函数,它将会调用从所有指令中搜集而来的link函数。
3.连接阶段
为了让视图成为动态的,Angular会对每一条指令运行一个link函数。link函数的一般操作是在DOM或者模型上创建监听器,监听器会使视图和模型的内容随时保持同步。
总结来说,后两个阶段就负责转换模板的编译阶段,以及修改视图中数据的连接阶段。在以上内容中,指令中的compile和link函数的主要不同点在于,compile函数用来对模板自身进行转换,而link函数负责在模型和视图之间进行动态关联。作用域在连接阶段才会被绑定到编译之后的link函数上,然后再通过数据绑定技术,指令就变成了动态的。
出于性能方面的考虑,这两个阶段是分开处理的。compile函数仅仅在编译阶段运行一次,而link函数会执行很多次——对于指令的每个实例,link函数都会执行一次。例如,在指令的上一层标签里面使用了ng-repeat,这里你并不想调用compile函数,因为那样会导致对ng-repeat中的每一次遍历都进行一次DOM-walk(DOM遍历)。希望只编译一次,然后再连接。
当然,你需要知道compile和link函数之间的不同点以及它们各自的功能。对于你将编写的大多数指令来说,并不需要对模板进行转换,所以,大部分情况下只要编写link函数就可以了。
为了进行比较,再分别看一下compile和link函数的语法。对于compile函数,语法是这样的:
compile: function compile(tElement, tAttrs, transclude) { return { pre: function preLink(scope, iElement, iAttrs, controller) {...}, post: function postLink(scope, iElement, iAttrs, controller) {...} } }
对于link函数,是这样:
link: function postLink(scope, iElement, iAttrs) {...}
注意这里有一个不同点,即link函数会访问scope(作用域)对象,而compile不会。这是因为,scope对象在编译阶段还不存在。当然你也可以在compile函数中返回link函数,这些link函数仍然可以访问scope对象。
同时请注意,compile和link都会接收到对应DOM元素的引用以及元素的属性列表。这里的不同点是,compile函数会接收模板元素及其属性列表,所以函数形参带有一个t前缀;而link函数会接收到视图实例对象,视图实例是使用模板创建的,所以函数形参带有一个i前缀。
只有当一个指令位于其他指令内部,而外部指令会生成模板的多份拷贝时,这种差异性才会显得比较重要。ng-repeat指令就是一个很好的例子。
DirectiveCompileAndLink.html
<html ng-app='app'> <body ng-controller='MyController'> <div ng-repeat='thing in things'> {{thing}}.<hello></hello> </div> </body> <script src="lib/angular/angular.js"></script> <script src="directive/CompileAndLink.js"></script> </html>
CompileAndLink.js
var appModule = angular.module('app', []); appModule.directive('hello', function() { return { restrict: 'E', template: '<span>Hi there</span>', replace: true }; }); appModule.controller('MyController',function($scope) { $scope.things = [1,2,3,4,5,6]; });
运行结果:
这里,compile函数只会被调用一次,而link函数的调用次数等于things中的元素个数——对于{{thing}}.<hello></hello>的每一份拷贝,它都会被调用一次。所以,如果对{{thing}}.<hello></hello>的每一份拷贝(实例)都有一些共同的东西需要修改,那么,出于效率方面的考虑,最好在compile函数里面来做这件事情。
你还会注意到,compile函数会接受一个transclude函数作为属性。如果你需要对内容进行变换,而简单的基于模板的变换并没有提供这种功能,那么,你可以在这里编写一个函数,使用程序的方式对内容进行变换。
最后,compile可以返回preLink和postLink函数,而link函数只会返回postLink函数。正如它的名称所表示的,preLink会在编译阶段之后、指令连接到子元素之前运行。类似的,postLink会在所有子元素指令都连接之后才运行。这就意味着,如果你需要修改DOM结构,你应该在postLink中来做这件事情,而如果在preLink中做这件事情会破环绑定过程,并导致错误。
五.扩展条实例Expander
我们经常需要在指令中访问scope对象,以便观察数据模型的值,当这些值发生变化时刷新UI。当你使用jQuery、Closure或其他类库来封装一些非Angular组件时,或者实现简单的DOM事件时,或者把一个Angular表达式传递给指令的属性然后执行时,都需要访问scope对象。
如果出于以上某种原因需要一个scope对象,那么scope对象的类型有以下三种选择:
1.指令对应的DOM元素上存在的scope对象。
2.可以创建一个新的scope对象,它继承了外层控制器的scope。在继承树中,位于当前scope对象上方的所有scope对象的值都可以被读取。对于DOM元素里面的任何其他指令,如果需要这种类型的scope,也可以共享这个scope,并且可以用它和树中其他scope进行通信。
3.使用独立的scope对象,它不会从父对象上继承模型的任何属性。当创建可复用的组件,并且需要把当前指令的操作和父scope隔离开时,你就需要使用这个选项。
可以使用以下配置语法来创建这些指令:
当创建独立scope时,默认情况是不可以访问父scope模型中的任何东西。但是,你可以指定把某些属性传递到你的指令中,你可以把这些属性名称看成函数的形参。
注意,虽然独立的scope不会继承模型的属性,但是它们仍然是父scope的孩子。与所有其他scope一样,它们带有一个指向父scope对象的$parent属性。
你可以通过传递属性名映射的方式把父scope中指定的属性传递给这个独立的scope。有三种方法可以在scope和父scope之间传递数据,我们把这三种方式叫做“绑定策略”,你还可以选择选择给属性名指定一个局部的别名。
不使用别名的语法形式为:
scope: {attributeName1: 'BINDING_STRATEGY', attributeName2: 'BINDING_STRATEGY',... }
使用别名的形式为:
scope: {attributeAlias: 'BINDING_STRATEGY' + 'templateAttributeName', ... }
绑定策略符号定义如下表所示:
下面通过一个具体例子来示范一下各用用法。例如,我们想要创建一个expander指令,它会显示一个很小的扩展条,点击的时候扩展条就会展开,显示额外的内容。
DirectiveExpander.html
<html ng-app='expanderModule'> <head> <meta http-equiv="content-type" content="text/html; charset=utf-8" /> <script src="lib/angular/angular.js"></script> <link rel="stylesheet" type="text/css" href="css/ExpanderSimple.css"/> </head> <body> <div ng-controller='SomeController'> <expander class='expander' expander-title='title'> {{text}} </expander> </div> </body> <script src="directive/ExpanderSimple.js"></script> </html>
ExpanderSimple.js
var expanderModule=angular.module('expanderModule', []) expanderModule.directive('expander', function() { return { restrict : 'EA', replace : true, transclude : true, scope : { title : '=expanderTitle' }, template : '<div>' + '<div class="title" ng-click="toggle()">{{title}}</div>' + '<div class="body" ng-show="showMe" ng-transclude></div>' + '</div>', link : function(scope, element, attrs) { scope.showMe = false; scope.toggle = function toggle() { scope.showMe = !scope.showMe; } } } }); expanderModule.controller('SomeController',function($scope) { $scope.title = '点击展开'; $scope.text = '这里是内部的内容。'; });
ExpanderSimple.css
.expander { border: 1px solid black; width: 250px; } .expander>.title { background-color: black; color: white; padding: .1em .3em; cursor: pointer; } .expander>.body { padding: .1em .3em; }
运行结果:
点击展开,效果如下:
我们先来看一看指令中的每一个选项分别会为我们做一些什么事情,如下所示:
如果觉得应该把把扩展条的标题定义在模板中,而不应该定义在数据模型中,那么你可以在scope的定义中使用字符串风格的属性,用@符号作为标志进行传递,示例如下:
scope: {title:'@expanderTitle'},
在模板中,我们可以使用下面这种方法达到同样的效果:
<expander class='expander' expander-title='Click me to expand'> {{text}} </expander>
注意,在使用@策略时,我们仍然可以通过双花括号插值语法把title绑定到控制器scope上:
<expander class='expander' expander-title='{{title}}'> {{text}} </expander>
六.扩展条实例Expander——操作DOM元素
可以向指令的link和compile函数传递iElement或tElement参数,这两个参数都是包装后的引用,它们都指向原始的DOM元素。如果你加载了jQuery库,那么它们就会指向经过jQuery包装之后的元素。
如果你不使用jQuery,那么这些DOM元素都位于Angular-native包装器jqLite中。jqLite是jQuery API的子集,在Angular中我们需要用它来创建所有东西。对于很多应用来说,只要使用这个轻量级API就可以实现所有你想做的事情了。
如果你需要直接访问原始的DOM元素,你可以使用element[0]来访问对象中的第一个元素。
在Angular文档中angular.element()部分,你可以看到目前能够支持的完整API列表,你可以使用angular.element()来创建包装在jqLite中的DOM元素。anuglar对象还带有addClass()、bind()、find()、toggleClass()等函数。当然,这些都是jQuery中常用的核心函数,只是Angular的实现代码更精致而已。
除了jQuery的API之外,元素还带有一些基于Angular的函数,如下所示。这些函数是否存在与你是否使用完整的jQuery库有关。
作为例子,重新实现一下扩展条的实例,新的实现方式不再借助于ng-show和ng-click指令,代码如下:
var expanderModule=angular.module('expanderModule', []) expanderModule.directive('expander', function() { return { restrict : 'EA', replace : true, transclude : true, scope : { title : '=expanderTitle' }, template : '<div>' + '<div class="title">{{title}}</div>' + '<div class="body closed" ng-transclude></div>' + '</div>', link : function(scope, element, attrs) { var titleElement = angular.element(element.children().eq(0)); var bodyElement = angular.element(element.children().eq(1)); titleElement.bind('click', toggle); function toggle() { bodyElement.toggleClass('closed'); } } } }); expanderModule.controller('SomeController',function($scope) { $scope.title = '点击展开'; $scope.text = '这里是内部的内容。'; });
从模板中删掉了ng-click和ng-show指令,作为替代,我们给title元素创建了一个jqLite元素,然后把toggle()函数绑定到它的click事件上作为回调,这样就能在用户点击扩展条title的时候执行必要的动作。在toggle()函数中,我们通过调用扩展条body元素上的toggleClass()方法来添加或者删除closed样式类。在toggleClass()函数中我们使用一个CSS样式类把元素设置为display:none,示例如下:
.closed { display: none; }
PS:jQuery 属性操作-toggleClass() 方法
toggleClass() 对设置或移除被选元素的一个或多个类进行切换。该方法检查每个元素中指定的类。如果不存在则添加类,如果已设置则删除之。这就是所谓的切换效果。
七.控制器及综合实例
要实现需要彼此通信的嵌套指令,可以使用控制器。<menu>需要知道内部<menu-item>元素的信息,这样它才能够正确地显示和隐藏它们。同样地,<tab-set>需要知道内部<tab>元素的信息;<grid-view>需要知道内部<grid-element>元素的信息。
如前所示,为了创建能够在指令之间进行通信的接口,你可以使用controller属性语法把控制器声明成指令的一部分:
controller: function controllerConstructor($scope, $element, $attrs, $transclude)
controller函数是通过依赖注入的,所以这里所列出的参数列表都是可选的,可以按照其他顺序将其列出,当然这些参数都具有某种潜在的用途。它们还是可用的服务子集。
通过require属性语法,其他指令可以把这个控制器传递给自已。require的完整形式如下:
require: '^?directiveName'
require字符串的解释如下表所示:
作为例子,我们来重写expander指令,让它可以用在一个"accordion"集合中。它会保证当你打开一个扩展条时,集合中的所有其他扩展条都会自动关闭掉。
Accordion.js
var expModule=angular.module('expanderModule',[]) expModule.directive('accordion', function() { return { restrict : 'EA', replace : true, transclude : true, template : '<div ng-transclude></div>', controller : function() { var expanders = []; this.gotOpened = function(selectedExpander) { angular.forEach(expanders, function(expander) { if (selectedExpander != expander) { expander.showMe = false; } }); } this.addExpander = function(expander) { expanders.push(expander); } } } }); expModule.directive('expander', function() { return { restrict : 'EA', replace : true, transclude : true, require : '^?accordion', scope : { title : '=expanderTitle' }, template : '<div>' + '<div class="title" ng-click="toggle()">{{title}}</div>' + '<div class="body" ng-show="showMe" ng-transclude></div>' + '</div>', link : function(scope, element, attrs, xccordionController) { scope.showMe = false; xccordionController.addExpander(scope); scope.toggle = function toggle() { scope.showMe = !scope.showMe; xccordionController.gotOpened(scope); } } } }); expModule.controller("SomeController",function($scope) { $scope.expanders = [{ title : 'Click me to expand', text : 'Hi there folks, I am the content that was hidden but is now shown.' }, { title : 'Click this', text : 'I am even better text than you have seen previously' }, { title : 'No,click me!', text : 'I am text that should be seen before seeing other texts' }]; });
accordion指令,它会做一些元素定位工作。我们把控制器的构造函数以及进行元素定位操作的方法添加到accordion指令中。accordion指令中定义了一个addExpander()函数,扩展条可以调用它来注册自身;还创建了一个可被扩展条调用的gotOpened()函数,通过它,accordion的控制器就知道需要把其他所有处于打开状态的扩展条都关闭。
在expander指令自身中,我们扩展它的时候要求accordion的控制器来自它的父元素,然后 在合适的时候调用addExpander()和gotOpened()函数。
注意,accordion指令中的控制器创建了一个API接口,有了它,所有扩展条控件之间就可以进行通信了。
编写模板来使用以上指令如下:
DirectiveAccordionExpander.html
<html ng-app="expanderModule"> <head> <meta http-equiv="content-type" content="text/html; charset=utf-8" /> <script src="lib/angular/angular.js"></script> <link rel="stylesheet" type="text/css" href="css/Accordion.css"/> </head> <body ng-controller='SomeController' > <accordion> <expander class='expander' ng-repeat='expander in expanders' expander-title='expander.title'> {{expander.text}} </expander> </accordion> </body> <script src="directive/Accordion.js"></script> </html>
Accordion.css
.expander { border: 1px solid black; width: 250px; } .expander>.title { background-color: black; color: white; padding: .1em .3em; cursor: pointer; } .expander>.body { padding: .1em .3em; } .closed { display: none; }
运行效果:
相关推荐
《用angularjs开发下一代Web应用》源码笔记 使用$rootScope.$broadcast来传递事件 reportBuilderCtrl里面广播事件: $scope.addChart = function() { console.log('reportBuilderCtrl:addChart'); $rootScope.$...
**AngularJS 框架详解** AngularJS 是一个强大的JavaScript框架,由Google维护,用于构建动态Web应用。它通过MVC(模型-视图-控制器...通过深入学习和实践,你可以有效地利用AngularJS构建高效、可维护的Web应用程序。
**学习路径**:文档建议初学者不必过于纠结概念和原理,在初步掌握如何使用AngularJS的基础上逐步深入理解其背后的设计理念和技术细节。特别是到了文档后期的“自定义指令”章节,读者将更加全面地了解AngularJS的...
尽管AngularJS被称之为工具而非完整的框架,但其设计核心却提供了前端应用开发的一种全新组织与开发方式。数据双向绑定是AngularJS最突出的特性之一,它允许视图层直接和数据模型层关联,模型数据的任何改变都能实时...
在提供的压缩包文件中,包含了多份AngularJS的学习资料,如"AngularJS学习笔记 - 进出自由,我的分享.mht"、"AngularJS开发指南"、"AngularJs学习笔记"和"AngularJS入门教程"。这些资源将帮助你深入理解AngularJS的...
**AngularJS学习笔记** AngularJS,作为一款强大的前端JavaScript框架,由Google维护,主要用于构建单页应用程序(SPA)。它的核心特性包括数据绑定、依赖注入、模块化和指令系统,极大地简化了网页应用的开发流程...
3. AngularJS应用开发:可能涉及到模块化、服务、过滤器、指令的创建与使用,以及如何实现路由和状态管理。 4. 集成与部署:这部分可能讲述了如何将Node.js、MongoDB和AngularJS整合在一起,形成完整的Web应用,并...
### AngularJS 学习笔记 #### 一、AngularJS 概述 AngularJS 是一个用于构建动态Web应用的开源框架,由 Google 维护。它通过扩展 HTML 的功能来简化 Web 开发,并允许开发者以声明式的方式编写代码,极大地提高了...
AngularJS是一款由Google维护的JavaScript框架,用于构建前端Web应用。这个"angularjs学习笔记本"很可能是包含了关于AngularJS的学习资料、示例代码和教程。AngularJS的核心特性是它使用了MVC(Model-View-...
它将详细介绍数据绑定和指令的基本用法,以及如何使用AngularJS进行简单的路由配置。此外,教程还会讲解如何利用$http服务进行异步数据获取,以及如何进行单元测试。 ### 四、AngularJs学习笔记 学习笔记通常包含...
移动互联网应用开发不仅限于Web应用,还包括原生应用的开发,如使用Android Studio和iOS的Swift或Objective-C。笔记可能讨论了跨平台框架,如React Native或Flutter,它们允许开发者用一种语言编写代码,同时在多个...
Flask之旅FlaskWeb开发基于Python的Web应用开发实战学习笔记
这个压缩包包含了作者个人的学习笔记和AngularJS的源码分析,这对于深入理解AngularJS的工作原理及其应用场景非常有帮助。让我们逐一探讨这些知识点。 1. **AngularJS基础**: AngularJS的核心特性包括数据绑定、...
AngularJS是一个开源的JavaScript框架,由Google维护,主要用于构建动态的Web应用程序。它的设计理念是将MVC(Model-View-Controller)模式应用于单页应用程序(SPA),使得前端开发更加高效、结构化。 **初始化...
本人在学习web开发中的个人学习笔记,其中包括Ext ssh javascript css 特效收藏 学习笔记
Java Web ppt 开发和J2SE学习笔记涵盖了Java编程的基础以及如何将其应用于Web应用程序的开发。J2SE,即Java 2 Platform, Standard Edition,是Java平台的核心部分,提供了用于开发和运行桌面应用、服务器端应用和...
【AngularJS学习笔记1】 AngularJS 是一个强大的前端JavaScript框架,用于构建动态Web应用程序。它通过数据绑定和依赖注入简化了HTML页面与JavaScript代码之间的交互。这篇笔记将深入探讨AngularJS的一些基本概念,...