Angular js是一个Web应用框架,它极其流行,已经成为目前使用最广泛的Javascript工具之一。Ionic基于AngularJS构建而成,所以学习一些AngularJS的知识很有必要。Ionic并没有独立开发一套完整的Web应用框架,而是对AngularJS进行了扩展,给它添加了大量界面组件和其他的移动端友好的特性。
本篇文章会带你了解AngularJS的核心知识并介绍一些基础知识。我们学会控制器,顾名思义,它会控制(control)你的数据。接着我们介绍作用于,它会连接控制器和用户界面,后者被称为视图(view)。仔细观察视图,你会看到他们是如何通过模板和作用域来创建交互视觉效果的。在这个过程中,我们还会学习其它特性,比如如何使用过滤器来转换数据、如何构建并使用指令来增强现有的HTML元素,以及如何从外部数据源中加载并保存应用数据。
现在我们来看一个bootstrap+AngularJS开发的一个小应用,方便我们了解AngularJS。你可以草丛GitHub上查看完整的项目代码https://github.com/ionic-in-action/chapter3。如果你想看最终的效果,可以访问https://ionic-in-action/chapter3.herokuapp.com
1.视图和模板:描述内容
AngularJS和HTML关系密切,尤其是在你创建模板的时候。模板是一块HTML内容,可以再需要的时候载入应用。AngularJS向HTML中加入了许多新特性并扩充了HTML的语义。视图会使用模板来展示数据。视图一定会有一个模板(就是HTML标签),还会有模板需要用到数据。视图会把模板转换成用户最终看到的视觉效果,也就是说,它会基于数据修改模板。下面我们来看一段模板
<ul class="list-group"> <li class="list-group-item" ng-repeat="note in notes" ng-click="displayNote($index)" ng-class="{active:note.id==content.id}"> {{note.title}}<br/> <small>{{note.date|date:'short'}}</small> </li> </ul>
这段模板展示的只是<ul>中的一个<li>元素,它包含多个被称为Angular指令的属性。指令会修改包含它的元素行为。在本例中,ng-Repeat会遍历一个Javascript对象或者数组,并为每个元素创建一个<li>元素。ng-click类似Javascript的onClick时间处理器,单击时会调用displayNote()函数。这个模板被渲染时会为notes数组中的每个元素创建一个列表元素。
双花括号({{}})表示某些数据会被展示在这里,这种思想被称为数据绑定,这种语法叫做表达式花括号中的所有内容都是表达式,Angular会用当前模型的数据对表达式求值。因此,note.title的内容会被插入到<li>元素中花括号包裹的位置。模板就是带有指令或表达式的HTML。视图会获取数据并使用数据中的值来对模板进行渲染。假设notes数组中有5个笔记,<ul>元素会包含5个列表元素,如图。
Angular有很多自带的指令,它们都以ng开头。有些用来修改显示样式(如ng-show、ng-class),有些用于表单(如ng-model、ng-Form),有些用于监听点击等各种事件(如ng-click、ng-mouseover)。Angular还有许多作用在原生HTML元素上的指令,提供一些HTML没有的功能,这些元素包括输入框、文本区域和锚点。举个例子,Angular可以给<input type="text">元素添加额外的属性,让它支持自定义验证。完整的列表请查阅Angular官方文档。
2.控制器、模型、和作用域:管理数据和逻辑
控制器是附加在文档对象模型(DOM)节点上的函数,用来驱动你的应用逻辑。在Javascript中,控制器就是一个函数,用来和作用域通信并响应时间。
作用域可以理解为在控制器和视图之间共享的一个上下文。可以把它看做控制器和界面的桥梁,作用域在控制器中更新时也会更新视图。作用域有两个核心角色:存储数据并允许控制器的方法访问数据。存储在作用域中的数据被称为模型。模型可以是任意Javascript值(同城是数组或者对象,也可以是简单的数据或者字符串),你可以把它存储在作用域中,然后通过作用域共享个控制器和视图。我们来看一个例子,它会把上图的视图和模板结合起来:
angular.module("App") .controller('Controller',function($scope){ $scope.notes=[ {id:1,title:'Note 1',date:new Date()} , {id:2,title:'Note 2',date:new Date()} ]; $scope.getNote=function(index){ $scope.content=$scope.notes[$index]; } });
这个控制器会使用一个数组中的元素来设置notes模型,后者存储在一个特殊的$scope对象中。这个对象是Angular提供的,每个作用域都有,你可以存储数据并在控制器和视图(也就是模型)中共享数据。视图会使用ng-repeat在列表中展示笔记数组。getNote()方法可以帮你声明哪些笔记需要存储在content模型中。视图可以调用这个方法,因为它们在同一个作用域中。
控制器中的所有内容都和应用的其他部分隔离,除了它自己的子作用域。这很重要,因为这可以限制代码和变量的可见性。对于一个新Angular开发这来说,常见的挑战就是访问不同作用域中的内容,默认情况下是不可能实现的。
Angular的作用域是有层级结构的,作用域可以想DOM一样嵌套。实际上,作用域对应页面上的DOM结构。作用域可以通过附加实现只允许一个HTML元素及其它子元素访问,就像CSS类可以将目标样式应用在设置类的元素及子元素一样。
当想进行跨作用域通信时,层级结构变得尤其重要,因为子作用域可以查看父作用域(就像Javascript的原型继承一样,如果你很熟悉的话)。Angular中的一些指令会创建子作用域,因此有时候不太好判断具体的作用域。如果你在子作用域中访问一个不存在的值,它实际上会在父作用域中寻找那个值,直到找到或者遍历完所有的父作用域。
根作用域(通过特殊的$root Scope对象访问)是Angular创建的第一个作用域,是其它所有作用域的基础。这意味着你放在根作用域上的所有东西对其它作用域都是可见的,听起来似乎还不错,但最好不要这样做。需要保持作用域整洁和聚焦,而不是把所有东西都堆在根作用域里。Javascript的作用域就有这个问题,应用通常使用全局作用域来保存变量。假设你有一个名为id的值;如果你的子作用域也有一个id,就会出现冲突,你无法访问根作用域中的值。协同开发时这个问题会更加突出,因为开发同一个应用的人越多或者你使用的外部工具越多,那就越有可能出现命名冲突。
注意:控制器不是万能的
有一些事实不应该在控制器中做的,因为它们会让你的代码更难维护和测试。最重要的是避免在控制器中进行DOM操作,假设你在构建一个幻灯片效果,控制器不应该改动DOM或者改变幻灯片的样式,因为这应该有自定义指令实现。你还应该避免在控制器中格式化或者过滤数据,可使用表单来做这些是。
3.Service:可重用的对象和方法
Angular中有一个概念交service,它本质上就是一个javascript对象,可以在整个应用中共享。Angular默认提供了许多service,你可以创建自己的service。如果你已经尝试过Angular,那你肯定用过自带的service。
$http是一个非常常见的service,Angular用它来操作HTTP请求。它有很多方法,比如get()、post()和其他的HTTP动作。service可以非常复杂(比如$http),也可以简单的只包含一些数据。
service是有Angular延迟加载的,也就是说,它们只会在使用的时候才载入内存。它们还是单例的,如果你在一个地方改变了service的值,其它用到这个service的地方都会受影响。Ionic把许多特性写成了Angular的service,需要记住的是,控制器中包含的几乎所有的内容都是service。
4.双向数据绑定:在控制器和视图之间共享数据
Angular最强大的特性之一就是双向数据绑定。你已经看到了视图如何把数据绑定到模板,其实反过来同样适用。视图可以改变作用域中的数据,数据会立刻更新到作用域并反应到控制器中。这在表单中尤其有用,用户向文本框中输入内容时,作用域中的值会同步更新。你不需要做任何特殊的事情来启动双向数据绑定——她会自动实现。