AngularJS 新手指南3

模版链接和图片

数据

注意 phones.json 文件包含了唯一的 Id 和图像 url,url 指向 app/img/phones/ 路径:

1
2
3
4
5
6
7
[
{
"id": "motorola-defy-with-motoblur",
"imageUrl": "img/phones/motorola-defy-with-motoblur.0.jpg",
"name": "Motorola DEFY\u2122 with MOTOBLUR\u2122",
},
]

模版

1
2
3
4
5
6
7
<ul class="phones">
<li ng-repeat="phone in phones | filter:query | orderBy:orderProp" class="thumbnail">
<a href="#/phones/\{\{phone.id}}" class="thumb"><img ng-src="\{\{phone.imageUrl}}"></a>
<a href="#/phones/\{\{phone.id}}">\{\{phone.name}}</a>
<p>\{\{phone.snippet}}</p>
</li>
</ul>

image 标签用了 ngSrc 指令,这一指令阻止浏览器按字面意思对待 angular {{表达式}},阻止它请求一个无效的 url http://localhost:8000/app/{{phone.imageUrl}}。使用 ngSrc 指令阻止浏览器发起无效的 http 请求。

实验

把 ngSrc 换成 src 属性,用 Firebug 之类的工具查看服务器访问日志,确认应用发起了无关的请求 /app/%7B%7Bphone.imageUrl%7D%7D (或 /app/{{phone.imageUrl}})。

多视图,路径规划和布局模版

我们准备把 index.html 变成整体布局模版,它将用于所有视图。其它具体的模版根据当前的“路径”—— 当前展示给用户的视图,包含在布局模版中。应用的路径通过 $routeProvider 定义,$routeProvider 是 $route 服务 的提供者。这一服务让你很轻松地把控制器,视图模版和当前的 URL 地址联系在一起。使用此特性,我们可以实现深层链接,通过深层链接可以利用浏览器的历史(后退前进导航)和书签。

DI,注入器和提供者笔记

如你所想,依赖注入 (DI)是 AngularJS 的核心特性,因此理解它的原理非常重要。

当应用启动时,Angular 创建了用于所有 DI 的注入器。注入器对 $http 或 $route 全然不知,实际上,它甚至不知道这些服务的存在,除非它被配置在合适的模块定义中。注入器的唯一责任是加载特定的模块定义,注册所有模块已定义的服务提供者,当被问及注入一个相关的特定函数时,它通过提供者实例化。

提供者是提供(创建)服务的实例,暴露配置 API的对象,它被用于控制服务的创建和运行时行为。比如 $route 服务,$routeProvider 暴露一些 API ,你可以用它为应用定义路径。

Angular 模块删除了全局状态,提供配置注入器的方法。与 AMD 或 require.js 截然相反,Angular 模块不试图解决脚本加载顺序或脚本懒加载的问题。模块系统不但可以相互依存,还可实现各自的目标。

App 模块

1
2
3
4
5
6
7
angular.module('phonecat', []).
config(['$routeProvider', function($routeProvider) {
$routeProvider.
when('/phones', {templateUrl: 'partials/phone-list.html', controller: PhoneListCtrl}).
when('/phones/:phoneId', {templateUrl: 'partials/phone-detail.html', controller: PhoneDetailCtrl}).
otherwise({redirectTo: '/phones'});
}]);

为了配置应用的路径,我们需要创建一个模块,取名为 phonecat 。我们使用 config API 把 $routeProvider 注入到我们的配置函数中,用 $routeProvider.when API 定义路径。

我们的应用路径定义:

  • 当 URL hash 为 /phones 时,手机列表视图将显示 。Angular 将用 phone-list.html 模版和 PhoneListCtrl 控制器构造手机列表视图。

  • 当 URL hash 匹配 ‘/phones/:phoneId’(phoneId 是URL 参数的一部分)时,手机详情视图将显示。Angular 将用 phone-detail.html 模版和 PhoneDetailCtrl 控制器构造手机详情视图。

我们复用以前创建的 PhoneListCtrl 控制器,在 app/js/controllers.js 文件增加新的 PhoneDetailCtrl 控制器。

当浏览器地址没有匹配其它的路径时, $route.otherwise({redirectTo: ‘/phones’}) 声明把地址重定向到 /phones。

注意第二个路径定义里 :phoneId 参数的使用,$route 服务用路径定义 —— ‘/phones/:phoneId’ —— 用来匹配当前的 URL。所有用冒号 (:)定义的参数被提取到$routeParams 对象。

为了让应用以新建的模块启动,我们需要把 ngApp 的值改成新定义模块的名字。

1
2
<!doctype html>
<html lang="en" ng-app="phonecat">

控制器

1
2
3
4
5
function PhoneDetailCtrl($scope, $routeParams) {
$scope.phoneId = $routeParams.phoneId;
}
//PhoneDetailCtrl.$inject = ['$scope', '$routeParams'];

模板

$route 服务通常结合 ngView 指令使用,ngView 指令为当前的路径把视图模版引入到布局模版中,它非常适合我们的 index.html 模版。

1
2
3
4
5
6
7
8
9
10
11
12
13
<html lang="en" ng-app="phonecat">
<head>
...
<script src="lib/angular/angular.js"></script>
<script src="js/app.js"></script>
<script src="js/controllers.js"></script>
</head>
<body>
<div ng-view></div>
</body>
</html>

注意到我们删除了 index.html 模板的大部分代码,换成了一个带有 ng-view 属性的 div。我们删除的代码放到了 phone-list.html 模版。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<div class="container-fluid">
<div class="row-fluid">
<div class="span2">
<!--Sidebar content-->
Search: <input ng-model="query">
Sort by:
<select ng-model="orderProp">
<option value="name">Alphabetical</option>
<option value="age">Newest</option>
</select>
</div>
<div class="span10">
<!--Body content-->
<ul class="phones">
<li ng-repeat="phone in phones | filter:query | orderBy:orderProp" class="thumbnail">
<a href="#/phones/\{\{phone.id}}" class="thumb"><img ng-src="\{\{phone.imageUrl}}"></a>
<a href="#/phones/\{\{phone.id}}">\{\{phone.name}}</a>
<p>\{\{phone.snippet}}</p>
</li>
</ul>
</div>
</div>
</div>

我们也为手机详情视图新建了模版:

1
TBD: detail view for &#123;&#123;phoneId}}

注意我们用的 phoneId 模型定义在 PhoneDetailCtrl 控制器。

实验

试着在 index.html 添加一个 {{orderProp}} 绑定,你到手机列表视图时,什么也不会发生,因为 orderProp 模型仅在 PhoneListCtrl 管理的作用域有效,它与 <div ng-view> 元素相关。如果往 phone-list.html 里面添加同样的绑定,绑定将生效。

更多模版

数据

除了 phones.json 外,app/phones/ 路径新加了包含每一手机信息的 json 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"additionalFeatures": "Contour Display, Near Field Communications (NFC),...",
"android": {
"os": "Android 2.3",
"ui": "Android"
},
...
"images": [
"img/phones/nexus-s.0.jpg",
"img/phones/nexus-s.1.jpg",
"img/phones/nexus-s.2.jpg",
"img/phones/nexus-s.3.jpg"
],
"storage": {
"flash": "16384MB",
"ram": "512MB"
}
}

每个文件用同样的数据结构,描述了手机的各种属性。

控制器

我们用 $http 服务扩展 PhoneDetailCtrl ,获取 json 文件。

1
2
3
4
5
6
7
function PhoneDetailCtrl($scope, $routeParams, $http) {
$http.get('phones/' + $routeParams.phoneId + '.json').success(function(data) {
$scope.phone = data;
});
}
//PhoneDetailCtrl.$inject = ['$scope', '$routeParams', '$http'];

我们通过 $route 服务,使用 $routeParams.phoneId 提取当前路径构建 URL, 用于 HTTP 请求。

模版

我们用了 angular {{表达式}} 和 ngRepeat 从模型获取手机数据,展现到视图中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<img ng-src="\{\{phone.images[0]}}" class="phone">
<h1>\{\{phone.name}}</h1>
<p>\{\{phone.description}}</p>
<ul class="phone-thumbs">
<li ng-repeat="img in phone.images">
<img ng-src="\{\{img}}">
</li>
</ul>
<ul class="specs">
<li>
<span>Availability and Networks</span>
<dl>
<dt>Availability</dt>
<dd ng-repeat="availability in phone.availability">\{\{availability}}</dd>
</dl>
</li>
...
</li>
<span>Additional Features</span>
<dd>\{\{phone.additionalFeatures}}</dd>
</li>
</ul>