模版链接和图片
数据
注意 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; }
|
模板
$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"> 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"> <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 {{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; }); }
|
我们通过 $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>
|