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

 

 

返回教程首页

Phone案例完整源码下载

使用Angular2建立一个可扩展单页应用

AngularJS专辑