AngularJS教程之四 phone完整进阶案例
之前我们基本已经掌握AngularJS的基本功能,下面以AngularJS官方教程的phone案例,Phone案例完整源码下载。在前面几节的基础上,进一步完善,加入多个REST URL请求 图片显示和点击放大等效果。
图片显示
首先我们在服务器返回的Json格式中增加图片:
[
{
...
"id": "motorola-defy-with-motoblur",
"imageUrl": "img/phones/motorola-defy-with-motoblur.0.jpg",
"name": "Motorola DEFY\u2122 with MOTOBLUR\u2122",
...
},
...
]
模板app/index.html:
<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>
与之前相比,多了标签: <img ng-src="{{phone.imageUrl}}">
多模板
该案例目前只有一个单一视图(列出所有手机的名单),只有一个模板代码位于index.html文件。下一步是添加一个新的视图,将显示名单中每个设备的详细信息。
通常实现两个模板是在index.html 中为两个视图增加模板代码,这容易导致混乱。这时我们将index.html转为所谓的布局模板layout template,其他模板都是嵌入在这个布局之中,这是依赖显示给用户的顺序路线route嵌入的。非常类似Struts的tiles模板。
应用的route是由$routeProvider提供,实质是由$route service提供,此服务可以很容易地将控制器、视图模板、浏览器当前的URL衔接在一起。利用这个功能,我们可以实现深层链接,它可以让我们利用浏览器的历史记录(向后和向前导航)和书签。
DI, Injector 和 Providers
当应用启动时, Angular创建一个injector注入器,用于给当前这个所有应用实现依赖注入,注入器injector只关心模块module. 它的职责是加载定义的模块module, 当有需要从模块中获得服务时,从提供者懒初始化一个实例,注入带有依赖的函数。见下图
提供者是一系列对象,提供或创建服务实例,并且将开放给API,调用API能够控制服务的创建和运行行为。 $route 服务是由 $routeProvider开放的API,允许你定义你自己应用的路由。
App 模块
在app/js/app.js加入route模块:
var phonecatApp = angular.module('phonecatApp', [
'ngRoute',
'phonecatControllers'
]);
phonecatApp.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'
});
}]);
这个非常类struts的struts-config.xml,配置每个命令响应和其对应的界面。实际上定义了URL与模板 以及控制器三者的关系,从而组成一个MVC流程。
为了配置我们的应用使用路由,需要创建一个模块,称为phonecatApp. 注意第二个尝试是模块数组 ['ngRoute', 'phonecatControllers']. 这是phonecatApp的两个依赖。此外我们还要将angular-route.js增加到index.html. 通过将这两个依赖模块声明在ofphonecatApp, 我们就能使用它们提供的标签和服务。
这样当使用config API 时,我们请求$routeProvider注入到我们的配置函数中,然后就能够使用其方法$routeProvider.when。
- 当通过/phones调用手机列表时,Angular将使用 phone-list.html 模板和控制器PhoneListCtrl 构建一个视图输出。
- 手机细节视图是通过url '/phone/:phoneId'调用,当URL提供phoneId时,查询某个具体的手机,Angular使用phone-detail.html 模板和PhoneDetailCtrl 控制器构建一个视图输出。
为了让我们的应用能够使用多模板,需要配置在app/index.html中配置模块:
<!doctype html>
<html lang="en" ng-app="phonecatApp">
...
控制器
有了前面配置的准备,我们可以编写完善控制器代码app/js/controllers.js::
var phonecatControllers = angular.module('phonecatControllers', []);
phonecatControllers.controller('PhoneListCtrl', ['$scope', '$http',
function ($scope, $http) {
$http.get('phones/phones.json').success(function(data) {
$scope.phones = data;
});
$scope.orderProp = 'age';
}]);
phonecatControllers.controller('PhoneDetailCtrl', ['$scope', '$routeParams',
function($scope, $routeParams) {
$scope.phoneId = $routeParams.phoneId;
}]);
在这个代码里,我们定义了两个控制器'PhoneListCtrl'和'PhoneDetailCtrl',名称分别对应于模块配置app.js中定义的控制器名称。
注意到$routeParams参数,是在前面route配置中 url的'/phones/:phoneId'有一个:phoneid,在":符号"中定义的任何变量都被加入到 $routeParams 对象,通过这个对象,我们在控制器中可以获得URL中phoneid的值。.
模板
$route通常和ngView 标签一起使用,这个标签能够将当前的路由的视图模板嵌入到布局模板中。模板app/index.html现在变成一个布局模板了,需要加载routeJs:
<!doctype html>
<html lang="en" ng-app="phonecatApp">
<head>
...
<script src="lib/angular/angular.js"></script>
<script src="lib/angular/angular-route.js"></script>
<script src="js/app.js"></script>
<script src="js/controllers.js"></script>
</head>
<body>
<div ng-view></div>
</body>
</html>
注意 ng-view标签中内容将被嵌入当前URL路由对应的模板页面,比如当前是查看手机列表,那么就是 templateUrl: 'partials/phone-list.html',
app/partials/phone-list.html:
<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>
如果是查询手机细节,那么当前路由是模板 templateUrl: 'partials/phone-detail.html',
更多模板代码见:Github
从服务器获得的Json也包含更多模型,这个模型中包含了手机产品的多个详细信息:
{
"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"
}
}
获得以上Json的控制器代码:
var phonecatControllers = angular.module('phonecatControllers',[]);
phonecatControllers.controller('PhoneDetailCtrl', ['$scope', '$routeParams', '$http',
function($scope, $routeParams, $http) {
$http.get('phones/' + $routeParams.phoneId + '.json').success(function(data) {
$scope.phone = data;
});
}]);
对应的手机详细信息模板:app/partials/phone-detail.html
<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>
优化过滤器
为了创建一个新的过滤器phonecatFilters ,我们首先需要创建一个模块,然后将我们的过滤器注册其中:
app/js/filters.js:
angular.module('phonecatFilters', []).filter('checkmark', function() {
return function(input) {
return input ? '\u2713' : '\u2718';
};
});
过滤器名称是checkmark,根据输入参数input真假返回两个字符串。
有了过滤器,下面是将这个过滤器作为一种依赖注册到主模块phonecat 中:
angular.module('phonecatApp', ['phonecatFilters']).
在模板中要增加加载这个过滤器:
app/index.html:
...
<script src="js/controllers.js"></script>
<script src="js/filters.js"></script>
过滤器使用语法是:
{{ expression | filter }}
在产品细节这个模板页面使用这个过滤器:
app/partials/phone-detail.html:
...
<dl>
<dt>Infrared</dt>
<dd>{{phone.connectivity.infrared | checkmark}}</dd>
<dt>GPS</dt>
<dd>{{phone.connectivity.gps | checkmark}}</dd>
</dl>
事件处理
我们在控制器代码增加一个方法,作为页面的事件消费者处理器:
app/js/controllers.js:
...
var phonecatControllers = angular.module('phonecatControllers',[]);
phonecatControllers.controller('PhoneDetailCtrl', ['$scope', '$routeParams', '$http',
function($scope, $routeParams, $http) {
$http.get('phones/' + $routeParams.phoneId + '.json').success(function(data) {
$scope.phone = data;
$scope.mainImageUrl = data.images[0];
});
$scope.setImage = function(imageUrl) {
$scope.mainImageUrl = imageUrl;
}
}]);
setImage是一个函数方法。
在模板中有:
app/partials/phone-detail.html:
<img ng-src="{{mainImageUrl}}" class="phone">
<ul class="phone-thumbs">
<li ng-repeat="img in phone.images">
<img ng-src="{{img}}" ng-click="setImage(img)">
</li>
</ul>
...
当点击图片时,执行setImage方法。
这部分代码见Github