本节书摘来自异步社区《AngularJS高级程序设计》一书中的第2章,第2.3节,作者:【美】Adam Freeman(弗里曼)著,更多章节内容可以访问云栖社区“异步社区”公众号查看
2.3 基本功能之外
我定义了基本的MVC构件,这样,就创建了一个本章开始时的静态模拟程序的动态版本。现在我们已经有了一个较为坚固的基础了,就可以使用一些更高级的技术来增添功能并创建一个更为完整的应用。在下面各节中,我将对这个待办事项应用使用不同的AngularJS特性,并解释将在本书中的何处更详细地介绍这些特性。
2.3.1 使用双向模型绑定
在前一节中所使用的绑定被称为单向绑定,其值是从模型中取得的,并用于操作模板中的元素。在Web应用开发中这是相当标准的做法和广泛应用的技术。例如,在使用jQuery工作时我经常使用Handlebars模板包,它提供了这种绑定,并且对于从数据对象中生成HTML内容十分有用。
AngularJS走得更远一步,还提供了双向绑定,模型用于生成元素,元素中的变化也能引起模型中的相应变化。为了演示双向绑定是如何实现的,我修改了todo.html文件以便用复选框代表每个任务的状态,如清单2-6所示。
清单2-6 向todo.html文件中增添复选框
我增添了一个新的td元素,以包含一个复选框式的input元素。重要的增加之处在于ng-model属性,用于告诉AngularJS在input元素和对应的数据对象(在生成元素时ng-repeat指令中赋给item的对象)的done属性之间创建一个双向绑定。
当HTML第一次被编译时,AngularJS将使用done属性的值来设置input元素的值,并且因为我使用的是复选框,值为true时将导致复选框被勾选上,值为false时将导致复选框被取消勾选。你可以使用浏览器加载todo.html文件来观察效果,如图2-4所示。你可以看到复选框的设置与true/false值的显示相匹配,该值是专门留在表格中用于帮助演示这一绑定特性的。
如果你勾选并取消勾选列表中的第一个项目的复选框,会发现双向绑定的神奇效果更为明显,你会注意到在下一列中的文本值也发生了变化。AngularJS绑定是动态的,而且是双向的,正如我对input元素所使用的一样,会更新模型,反过来也会更新其他具有相关数据绑定的元素。在本例中,input元素和最右边一列可以无缝地保持同步,如图2-5所示。
双向绑定可被应用到接收用户输入的元素上,一般即是与HTML表单元素相关联的元素,在第12章中将会深入介绍该话题。具有灵活而动态的模型使得使用AngularJS创建复杂应用程序变得简单,在整本书中你将看到许多例子演示AngularJS的灵活性。
2.3.2 创建和使用控制器行为
控制器在作用域上定义行为。行为是对模型中的数据进行操作的函数,用于实现应用程序中的业务逻辑。控制器定义的行为用于向视图提供数据并显示给用户,并且根据用户交互更新模型。
为了演示一个简单的行为,我打算修改todo.html的header中右边显示的标签,以便使其仅显示未完成待办事项的个数。在清单2-7中你可以看到实现这些所需做的修改。(我还移除了包含true和false值的列,该列仅是用于展示数据绑定在数据模型中所引起的变化的。)
清单2-7 在todo.html文件中定义和使用控制器行为
行为是通过在传入给控制器函数的$scope对象上添加函数而定义的。在这份清单中,我定义了一个函数用于返回未完成项目的数量,该数量是通过遍历$scope.todo.items数组中的对象,对于done属性为false的对象进行计数而得到的。
向$scope对象添加的函数所赋给的属性名称,被用作行为名。行为名叫作incompleteCount,我可以在ng-controller属性的作用域中调用它,ng-controller属性会将控制器应用到构成视图的HTML元素上。
在清单2-7中我将incompleteCount行为使用了两次。第一次是作为一个简单的数据绑定用于显示项目个数,如下:
注意,我调用该行为时使用了圆括号。你可以将对象作为参数传递给行为,这使得创建可使用不同数据对象的通用行为成为可能。我的应用程序足够的简单,因此我决定不传入任何参数,而是直接从控制器的$scope对象中直接获取所需数据。
我还结合指令使用了该行为,类似这样:
如果赋给属性值的表达式计算结果为true时,ng-hide指令将会隐藏所使用到的元素(及其内容中的元素)。在这里,我调用了incompleteCount行为检查未完成项目的个数是否为零。如果是的话,清单中显示项目个数的标签将会对用户隐藏。
如图2-6所示,可以通过使用浏览器加载todo.html文件来察看这个指令和应用程序的效果。在清单中勾选和取消勾选项目,将会改变计数器标签所显示的项目个数,选中所有项目则会使得计数器标签被隐藏掉。
2.3.3 使用依赖于其他行为的行为
始终贯穿于AngularJS的主题之一便是HTML、CSS和JavaScript等的潜在特性是如何被吸纳到Web应用程序开发中的。举个例子,因为行为是通过使用JavaScript函数而创建的,所以你可以在同一个控制器中其他行为所提供的能力的基础上创建其他行为。在清单2-8中,我创建了一个行为,可以根据待办事项列表中的未完成项数来选择CSS类。
清单2-8 在todo.html文件中以其他行为为基础构建行为
我定义了一个名为warningLevel的新行为,该行为基于未完事项的数目返回一个Bootstrap CSS类名,而未完成事项数是通过调用incompleteCount行为而得到的。这种方式减少了控制器中的重复逻辑,正如在第25章中将会看到的,这样有助于简化单元测试的过程。
我使用ng-class指令来应用warningLevel行为,如下:
这个指令应用了该行为所返回的CSS类,具有改变HTML文档中标签颜色的效果,如图2-7所示。(在第2部分中我将完整介绍AngularJS指令,并在第15~17章中演示如何创建自己的指令。)
2.3.4 响应用户交互
你已经见到了行为和指令是如何结合在一起创建应用特性的,也正是在这样的组合驱动之下产生了AngularJS应用中的许多功能。最强大的组合之一便是将指令和行为用于响应用户交互。在清单2-9中,你可以看到我对todo.html文件增加的内容,以使得用户能够创建新的待办事项项目。
清单2-9 在todo.html文件中响应用户输入
我增加了一个名为addNewItem的行为,能够取得新的待办事项的文本并向数据模型中添加一个对象,将该文本用作action属性的值并设置done属性为false,类似这样:
这是我向你演示的第一个对模型进行修改的行为,但是在实际项目中经常会划分得更明确一些,将行为划分为从视图获取和准备数据,以及响应用户交互并更新模型。注意这个行为仍然是定义为标准JavaScript函数,因此可以使用JavaScript对数组支持的push方法来更新模型。
本例中的神奇之处在于对指令的两处使用。这里是其中第一处:
这是与设置复选框时所使用的同一个ng-model指令,当使用表单元素时将会多次遇到这个指令。需要注意的是我为指令指定了一个属性名,用于更新本不是模型的一部分。ng-model指令将会在控制器的作用域中为我动态地创建这个属性,实际上是创建出了用于处理用户输入的动态模型属性。在本例中增添的第二处指令里我使用了这个动态属性:
ng-click指令设置了一个当click事件被触发时的处理器,将会计算一个表达式。在这里,该表达式调用addNewItem行为,传入动态的actionText属性作为参数。这样的效果是增添了一个新的待办事项项目,包含有用户输入到input元素中的文本,如图2-8所示。
2.3.5 对模型数据过滤和排序
在第14章中,我将会介绍AngularJS的过滤器特性,该特性为准备在视图中要显示的模型数据提供了一个极好的方式,而不用额外创建行为。使用行为没有什么不好的,但是过滤器更倾向于通用目的,并提供在整个应用中进行复用的能力。清单2-10显示了对todo.html文件所做的修改,以演示过滤器功能。
清单2-10 在todo.html文件中增加过滤器功能
过滤器可被应用于数据模型的任何部分,在这里你能够看到我使用了过滤器来控制被ng-repeat指令用于操作table元素中待办事项列表项目详情信息的数据。我使用了两个过滤器:filter(对于这么有用的组件来说真是个恼人的名字)过滤器和orderBy过滤器。
filter过滤器基于所配置的条件筛选对象。我使用filter选出那些done属性为false的项,所具有的效果就是任何已完成的项将不会被显示给用户。orderBy过滤器对数据项进行排序,我使用它按照action属性的值进行排序。在第14章中将详细解释过滤器,但是你可以通过在浏览器中加载todo.html文件来查看所创建的效果,添加一个新项,然后选中Done复选框,如图2-9所示。
当你添加一个新项时,将会根据字母顺序将其插入到列表中,然后当你勾选了复选框时,该项将会被隐藏。(模型中的数据并未被排序,排序操作是在ng-repeat指令处理并创建表中的各行时执行的。)
改进过滤器
前一个例子演示了过滤器特性是如何工作的,但结果却没有多大意义,因为被勾选中的项会永远地对用户隐藏。幸运的是,创建一个自定义过滤器是件简单的事,如清单2-11所示。
清单2-11 在todo.html文件中创建自定义过滤器
AngularJS模块对象所定义的filter方法用于创建一个过滤器工厂,该工厂会返回一个函数用于过滤一组数据对象。目前暂时不要担心工厂这部分的细节,只需知道使用filter方法需要传入一个函数,该函数中需要一个能够返回过滤后数据的函数就够了。我对过滤器所起的名字是checkedItems,执行实际的过滤功能的函数有两个参数:
参数items是由AngularJS提供的,是应当被过滤的对象集合。在使用过滤器时我将提供showComplete参数的值,该值用于决定已经被标记为完成的项是否会被包含在过滤后的数据中。在清单2-12中你可以看到我是如何使用自定义过滤器的。
清单2-12 在todo.html文件中使用自定义过滤器
我增加了一个复选框,使用ng-model指令来设置一个名为showComplete的模型值,该值通过表格中的ng-repeat指令传给我的自定义过滤器:
自定义过滤器的语法与内置过滤器所支持的语法相同。我指定了通过filter方法所创建的过滤器的名称,随后跟一个冒号(:),然后跟随着我要传给过滤器函数的模型属性名。我指定为showComplete模型属性,也就是用于控制被选中项可见性的复选框的状态。在图2-10中可以看到效果。
2.3.6 通过Ajax获取数据
我要做的最后一项修改是通过一个Ajax请求以JSON数据形式获取待办事项列表的数据。(如果不熟悉JSON的话,在第5章中将会有介绍)我在angularjs文件夹下创建了一个名为todo.json的文件,在清单2-13中可以看到其内容。
清单2-13 todo.json文件的内容
正如你所看到的,JSON数据形式与JavaScript字面量对象的定义形式非常相似,这也是为什么JSON成为Web应用的首选数据的原因之一。在清单2-14中,你可以看到我对todo.html文件所做的修改,以从todo.json文件加载数据,而不是使用本地定义的数组。
清单2-14 在todo.html文件中发起Ajax请求获取JSON数据
我从静态定义的数据模型中移除了items数组,并增加了一个对run方法的调用,该方法是由AngularJS模型对象所定义的。run方法接受一个函数,并仅在AngularJS执行完初始化设置后运行一次,常用于一次性的任务。
我对传给run方法的函数指定了$http作为参数,告诉AngularJS我要使用对Ajax请求提供支持的服务对象。这种使用参数告诉AngularJS需要哪些特性的方法,是被称为依赖注入的方法的一部分,在第9章中将加以介绍。
$http服务提供了对底层Ajax请求的访问功能。与用于和RESTful的Web服务交互的$resource服务相比较,这里的底层显得并不那么底层。(在第3章中将介绍REST,第21章中将介绍$resource服务对象。)我使用$http.get方法来创建一个HTTP GET请求,向服务器请求todo.json文件:
从get方法得到的结果是一个promise对象,该对象用于表示将在未来完成的工作。在第5章中将解释promise对象是如何工作的,第20章中还将深入其细节,但对于本章来说知道在promise对象上调用success方法能够起什么作用就够了,调用success方法能够让我指定一个将在发往服务器的Ajax请求已完成时被调用的函数,而从服务器获取到的JSON数据将会被解析并创建一个JavaScript对象,并传入给我的success函数作为data参数。我使用收到的data参数对模型进行更新:
如果是在浏览器中查看todo.html文件,你不会注意到任何差异,但是数据却是通过第二次HTTP请求从服务器上获得的。你可以使用F12工具在网络连接报告上看到这些变化,如图2-11所示。
通过查看浏览器证明了Ajax确实被调到了,这一事实显示了AngularJS使用远程文件和数据进行工作变得多么简单。这个主题在全书中将会反复重新提到,因为它为AngularJS创建更复杂的Web应用所提供的许多特性加强了基础。