本章,带你体验一个简单的开发流程,将一个静态的使用模拟数据的应用,变成具有AngularJS特性的动态web应用。在6-8章,作者将展示如何创建一个更复杂,更真实的AngularJS应用。
1、准备项目
在项目路径下,新建todo.html,代码如下:
<!DOCTYPE html>
<html data-ng-app>
<head>
<title>TO DO List</title>
<link href="bootstrap.css" rel="stylesheet" />
<link href="bootstrap-theme.css" rel="stylesheet" />
</head>
<body>
<div class="page-header">
<h1>Adam's To Do List</h1>
</div>
<div class="panel">
<div class="input-group">
<input class="form-control" />
<span class="input-group-btn">
<button class="btn btn-default">Add</button>
</span>
</div>
<table class="table table-striped">
<thead>
<tr>
<th>Description</th>
<th>Done</th>
</tr>
</thead>
<tbody>
<tr><td>Buy Flowers</td><td>No</td></tr>
<tr><td>Get Shoes</td><td>No</td></tr>
<tr><td>Collect Tickets</td><td>Yes</td></tr>
<tr><td>Call Joe</td><td>No</td></tr>
</tbody>
</table>
</div>
</body>
</html>
该静态页面已经正常显示。到第六章时,作者会讲应用的文件结构。
2、使用AngularJS
用户能用该网页,查看要做的事项的列表,检查事项是否完成,创建一个新的事项。
2.1、应用AngularJS到HTML文件
<html ng-app="todoApp">
<head>
<title>TO DO List</title>
<link href="bootstrap.css" rel="stylesheet" />
<link href="bootstrap-theme.css" rel="stylesheet" />
<script src="angular.js"></script>
<script>
var todoApp = angular.module("todoApp", []);
</script>
</head>
AngularJS apps,从一个或多个模块开始。模块是通过调用angular.module方法创建的,如下面那样:
var todoApp = angular.module("todoApp", []);
作者将在第9和第18章描述模块的属性。angular.module方法的参数,是要创建的模块的名字,和模块需要的其他东西的数组。作者创建了一个叫做todoApp的模块。遵循约定,模块的名字以App结尾。因为不需要AngularJS提供其他模块,所以传递空数组作为第二个参数。作者将在第18章,展示不同模块可用的特性。
作者通过ng-app属性,告诉AngularJS如何应用模块。AngularJS通过给HTML添加新的元素、属性、类和special comments,来扩展HTML,才能工作。AngularJS库动态编译HTML,为了定位和处理这些附加信息,并创建一个应用。你可以通过JavaScript代码,客制化应用的行为,补充内建功能,定义你自己的附加信息到HTML上。
注意:AngularJS的编译器,不像你碰到的C#或Java项目的编译器那样,处理源代码,为了生成可以执行的运行时输出。精确地说,当浏览器已经加载内容,AngularJS库评估HTML元素,并使用标准DOM API和JavaScript特性,来添加或一处元素,设置事件处理器,等等。在AngularJS开发中,没有明确的变异步骤,只需要修改你的HTML和Javascript文件,并用浏览器加载他们。
AngularJS附加到HTML上,最重要的就是ng-app属性,它制定该html元素列表包含一个应该被AngularJS编译和处理的模块。当使用的JavaScript框架只有AngularJS时,习惯将ng-app属性应用到html元素。如果你要将AngularJS和其他技术混合,如jQuery,你可以通过在文档上的一个元素上应用ng-app属性,来收缩AngularJS app的边界。
2.2、给HTML应用AngularJS
给HTML文档添加一个非标准的属性,看起来很奇怪,特别是如果你已经写过一些web apps,并严格遵守HTML标准。有一个替代方式,使用data属性,给AngularJS添加data-前缀。
<html data-ng-app="todoApp">
作者会继续使用AngularJS约定,并使用ng-app属性,和其他HTML增强属性。他建议咱们也这样做。如果你的开发工具不能处理非标准的HTML元素和属性,你也可以不这么做。
3、创建一个数据模型
AngularJS支持MVC模式。作者将在第3章讲MVC。简单来讲,遵循MVC模式,需要你把应用分成三个确切的区域:应用中数据(model),对数据的操作逻辑(controllers),显示数据的逻辑(Views)。
<script>
var model = {
user: "Adam",
items: [{ action: "Buy Flowers", done: false },
{ action: "Get Shoes", done: false },
{ action: "Collect Tickets", done: true },
{ action: "Call Joe", done: false }]
};
var todoApp = angular.module("todoApp", []);
</script>
提示:作者说,model也可以包含create,load,store,modify数据对象所必须的逻辑。在一个AngularJS应用中,该逻辑一般放在服务器,被web server访问。作者将在第3章详细介绍。
提示:在任何AngularJS开发项目中,你不得不定义MVC主要部分的作用域。作者保证,在第6章可以看到一个大型的例子。它有很多初始化设置和必须的配置。
4、创建一个Controller
Controller定义了业务逻辑,为view提供支持。定义一个controller的最好方法,是解释什么种类的逻辑,是它不支持的,而什么是它支持的。
逻辑,是处理存储或获取模型的部分数据。逻辑,处理格式化过的数据,将它作为VIEW的一部分,显示给用户。Controller在model和view之间,并连接他们。Controller相应用户交互,更新model中的数据,提供view和view必须的数据。
作者说,不用担心,本书结束时,你将学会MVC模式,和在AngularJS中应用他们。
通过调用angular.module返回的模型对象的controller方法,创建一个controller。controller方法的参数,是新controller的名字,和他的功能。
todoApp.controller("ToDoCtrl", function ($scope) {
$scope.todo = model;
});
</script>
</head>
<body ng-controller="ToDoCtrl">
controller的命名约定,是以Ctrl结尾。
你不会总是想让views访问整个model,所以你希望使用controller,来明确地选择一部分数据,这叫做scope(范围)。
我的controller函数的参数,调用了$scope,这就是说,$符号跟随者单词scope。在AngularJS app中,以$开始的变量名,表示是AngularJS提供的内建特性。当你看到$符号,它一般指向一个内建的服务,它是一个自包含的组建,为多个控制器提供特性。但$scope很特别,它用于曝露数据和函数给view。作者将在第13章讲解scope,在18-25章讲解内建services。
对于本应用而言,作者系统它在view中使用整个model,所以他定义了一个属性,在$scope服务对象上,调用todo,并像下面那样,指派完整的model:
$scope.todo = model;
作者也不得不指定controller负责的html文档的区域。因为是一个简单的应用,并且只有一个controller,作者就在body元素上应用ng-controller。
<body ng-controller="ToDoCtrl">
ng-controller属性的值,是要设置的controller的名字。作者将在第13章深入controller的主题。
4、创建一个View
作者将在下面的代码中,演示如何使用一种注释类型,也叫做数据绑定。
<h1>
{{todo.user}}'s To Do List
<span class="label label-default">{{todo.items.length}}</span>
</h1> <tbody>
<tr ng-repeat="item in todo.items">
<td>{{item.action}}</td>
<td>{{item.done}}</td>
</tr>
</tbody>
5、插入Model View
AngularJS使用双花括号{{}},来代表一个数据绑定表达式。表达式的内容,
作为JavaScript,被求值,通过使用Controller,来限制数据和功能被分配给scope。
在本例中,作者只能访问它在定义controller时,使用在$scope对象创建的属性名,来访问指派给$scope对象的那部分数据。
这意味着,例如,如果作者想访问model.user属性,作者定义一个数据绑定表达式,
引用todo.user。这就是为什么,作者给$scope.todo属性指派model对象。
AngularJS编译文档中的HTML,发现ng-controller属性,让ToDoCtrl函数设置用于创建view的范围。遇到的每个数据绑定表达式,AngularJS在$scope对象中查找指定的值,
并添加该值到HTML文档。例如,下面的例子:
{{todo.user}}'s To Do List
它会被处理,并翻译为下面的字符串:
Adam's To Do List
这就是所谓的数据绑定或模型绑定,model中的值,绑定到一个html元素的内容上。这里有一些不同的方式来创建数据绑定,作者将在第10章解释。
6、求表达式的值
一个数据绑定表达式的内容,可以是任何可用的JavaScript声明,意味着你能执行操作,从model中创建新的数据。下面是显示to-do items的个数:
<div class="page-header">
{{todo.user}}'s To Do List<span class="label label-default">{{todo.items.length}}</span>
</div>
提示:在表达式中,应该只执行简单的操作。不要使用数据绑定,来执行复杂逻辑或对model的操作。这应该是controller的工作。你会经常遇到很难讲逻辑归类于view或controller的情况。作者的建议是,不要担心它。以最快的速度开发,再以后需要时再移动这些逻辑。如果实在不知道该怎么做,就把逻辑放在controller中,它有60%的几率是正确的。
7、使用指令
表达式中也可以使用指令,这会告诉AngularJS,你想要怎么处理内容。在本课中,作者使用ng-repeat属性。
<tr ng-repeat="item in todo.items">
<td>{{item.action}}</td><td>{{item.done}}</td>
</tr>
它会产生下面的代码
<tr ng-repeat="item in todo.items" class="ng-scope">
<td class="ng-binding">Buy Flowers</td>
<td class="ng-binding">false</td>
</tr>
<tr ng-repeat="item in todo.items" class="ng-scope">
<td class="ng-binding">Get Shoes</td>
<td class="ng-binding">false</td>
</tr>
<tr ng-repeat="item in todo.items" class="ng-scope">
<td class="ng-binding">Collect Tickets</td>
<td class="ng-binding">true</td>
</tr>
<tr ng-repeat="item in todo.items" class="ng-scope">
<td class="ng-binding">Call Joe</td>
<td class="ng-binding">false</td>
</tr>
ng-repeat指令,会是你最常用的指令之一。
8、超越基本
8.1使用双向模型绑定
作者之前使用的绑定,都是单向绑定,从model中取出值,用于在模板中分发这些元素。这是一个相当基本的材料,并且在web app开发中,广泛使用的技术。例如,作者在使用jQeury时,经常使用Handlebars模板包,它提供该类型的绑定,对从数据对象生成HTML内容很有用。
AngularJS也提供双向绑定,用模型生成元素,当元素改变时,导致模型也相应的改变。
<tr ng-repeat="item in todo.items">
<td>{{item.action}}</td>
<td><input type="checkbox" ng-model="item.done" /></td>
<td>{{item.done}}</td>
</tr>
作者添加了一个checkbox input元素。重要的附加信息,是ng-model属性,它告诉AngularJS,在input元素的值和相应数据对象的done属性之间,创建一个双向绑定。
当HTML第一次编译,AngularJS会使用done属性的value,设置input元素的value。
AngularJS的绑定是动态的,双向的绑定。两个元素绑定到同一个,一个元素变了,另一个也会变。
双向绑定,可以应用于用户输入的元素,生成使用HTML表单元素关联的有意义的元素。作者会在第12章深度讲解。
8.2、创建并使用控制器行为
控制器在范围上定义行为。行为,是操作model上的数据,以实现应用中的业务逻辑的功能。行为,通过controller定义,支持一个view,以显示数据,并基于用户交互更新模型。
todoApp.controller("ToDoCtrl", function ($scope) {
$scope.todo = model;
$scope.incompleteCount = function () {
var count = 0;
angular.forEach($scope.todo.items, function (item) {
if (!item.done) { count++ }
});
return count;
}
}); <div class="page-header">
<h1>
{{todo.user}}'s To Do List
<span class="label label-default" ng-hide="incompleteCount() == 0">
{{incompleteCount()}}
</span>
</h1>
</div>
通过给传递给controller 函数的$scope对象添加功能,来定义行为。
提示:作者使用angular.forEach方法,来枚举数组的内容。AngularJS包含许多有用的工具方法,补充JavaScript语言。作者将在第4章描述这些工具方法。
附加在$scope对象上的属性名,作为行为的名字。作者的行为叫做incompleteCount,可以在ng-countroller属性的范围内,调用他。
ng-hide指令的表达式为true时,会隐藏元素。
提示:ng-hide是基于AngularJS model状态自动操作DOM的大量指令中的一个。作者会在第11章详细讲解,并在15-17章教咱们如何创建自己的指令。
8.3、使用其他行为
$scope.warningLevel = function () {
return $scope.incompleteCount() < 3 ? "label-success" : "label-warning";
}<div class="page-header">
<h1>
{{todo.user}}'s To Do List
<span class="label label-default" ng-class="warningLevel()"
ng-hide="incompleteCount() == 0">
{{incompleteCount()}}
</span>
</h1>
</div>
作者定义了一个叫做warningLevel的行为,它基于完成的to-do items的个数,返回Bootstrap CSS class的名字。
8.4相应用户交互
你已经看到如何将行为和指令结合在一起,创建app 特性,这种结合,将派生许多AngularJS app的功能。一个最强大的结合,是实现响应用户的交互。
$scope.addNewItem = function (actionText) {
$scope.todo.items.push({ action: actionText, done: false });
}
<div class="input-group">
<input class="form-control" ng-model="actionText" />
<span class="input-group-btn">
<button class="btn btn-default"
ng-click="addNewItem(actionText)">Add</button>
</span>
</div>
作者添加了一个叫做addNewItem的行为,它持有一个新to-do item的文本,并添加一个对象到数据模型,使用该文本作为action属性的值,并设置done属性为false。
这是作者展示的第一个修改model的行为。注意,该行为依然作为一个标准的Javascript函数被定义。
ng-click指令,这只一个处理器,当click事件被处罚,计算表达式的值。在本例中,表达式指向addNewItem行为,传递动态actionText属性作为参数。
8.5过滤和排序模型数据
在第14章,作者介绍了AngularJS过滤特性。
<tr ng-repeat="item in todo.items | filter:{done: false}
| orderBy:'action'">
<td>{{item.action}}</td>
<td><input type="checkbox" ng-model="item.done" /></td>
</tr>
过滤可以应用到数据模型的任何部分。
提示:作者在orderBy里,使用字符串,作为属性值。使用’‘单引号。默认地,AngularJS假设scope定义的任何东西都是一个属性,并且不用引号,会查找一个叫做action的scope属性。这在你以编程的方式定义值时,会很有用。但是不意味着你不得不记住使用字符值,当你想要制定一个常熟。
model中的数据并没有被排序,排序操作时在ng-repeat指令用于执行创建table的row时,执行的。
8.6、改善过滤器
todoApp.filter("checkedItems", function () {
return function (items, showComplete) {
var resultArr = [];
angular.forEach(items, function (item) {
if (item.done == false || showComplete == true) {
resultArr.push(item);
}
});
return resultArr;
}
});
该过滤方法创建了一个过滤fatory,它返回一个功能,用于过滤一组数据对象。items参数,由AngularJS提供,是一组要被过滤的对象。当作者应用过滤器时,他会为showComplete参数提供一个值,它用于表明,是否显示已经完成的人物。
<tbody>
<tr ng-repeat=
"item in todo.items | checkedItems:showComplete | orderBy:'action'">
<td>{{item.action}}</td>
<td><input type="checkbox" ng-model="item.done" /></td>
</tr>
</tbody>
</table>
<div class="checkbox-inline">
<label><input type="checkbox" ng_model="showComplete">
Show Complete</label>
</div>
这个自定义的过滤器,和内建的过滤器一样。我使用filter作为过滤器的方法名,使用一个:,后面跟随一个我想要传递给过滤器函数的模型属性名。
8.7通过Ajax获取数据
作者将在第五章讲解JSON。创建一个todo.json文件,内容如下:
[{ "action": "Buy Flowers", "done": false },
{ "action": "Get Shoes", "done": false },
{ "action": "Collect Tickets", "done": true },
{ "action": "Call Joe", "done": false }]
<PRE><SPAN style="COLOR: #000000">var model = {
user: "Adam"
};
var todoApp = angular.module("todoApp", []);
todoApp.run(function ($http) {
$http.get("todo.json").success(function (data) {
model.items = data;
});
});</SPAN></PRE>
调用AngularJS模块对象定义的run方法,该方法执行一个函数,它只有AngularJS已经执行初始化设置后执行一次,用于one-off任务。
我制定$http,作为传递给run方法的函数的参数,它告诉AngularJS,我想要使用为Ajax调用提供支持的服务对象。使用参数告诉AngularJS,你需要哪个特性,这叫做依赖注入。作者将在第9章介绍。
$http服务提供访问低级Ajax请求。低级在本例中,并不低。除非和$resources服务作比较。该服务用于和RESTful web services互动。作者将在第3章讲REST,在第12章讲$resource。作者使用$http.get方法,做了一个HTTP GET请求,从服务要todo.json文件。
get方法的结果,是一个许诺,他是一个对象。作者会在第5章讲解,在第20章深入讲解。在许诺对象上,调用success方法,当Ajax请求完成时,JSON数据从服务端返回,转换为一个Javascript对象,传递到作者的success函数作为data参数。作者使用收到的数据,更新模型。