1.切换分支到step7,并启动项目
git checkout step-7npm start
2.需求:
在步骤7之前,应用只给我们的用户提供了一个简单的界面(一张所有手机的列表),并且所有的模板代码位于index.html
文件中。下一步是增加一个能够显示我们列表中每一部手机详细信息的页面。可以先看一下step6和7的 .
为了增加详细信息视图,我们可以拓展index.html
来同时包含两个视图的模板代码,但是这样会很快给我们带来巨大的麻烦。相反,我们要把index.html
模板转变成“布局模板”。这是我们应用所有视图的通用模板。其他的“局部布局模板”随后根据当前的“路由”被充填入,从而形成一个完整视图展示给用户。
AngularJS中应用的路由通过来声明,它是服务的提供者。这项服务使得控制器、视图模板与当前浏览器的URL可以轻易集成。应用这个特性我们就可以实现,它允许我们使用浏览器的历史(回退或者前进导航)和书签。
3.效果:
可以很明显的注意到当访问 http://localhost:8000/app时,其URL被重定向到了http://localhost:8000/app/#/phones页面.
4.实现代码
app/index.html
Google Phone Gallery
可以发现其实现代码非常简单,只有一个div标签, 然后一个ng-view指令.同时要注意的是引入了angular.js、angular-route.js、app.js和controllers.js,这里将按照顺序贴出其js代码.并加以说明.
app/app.js
'use strict';/* App Module */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' }); }]);
app/controllers.js
'use strict';/* Controllers */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; }]);
app/partials/phone-detail.html
:
TBD: detail view for { {phoneId}}
代码说明:
1).index.html中<html lang="en" ng-app="phonecatApp">定义了要使用的ng-app是"phoneApp",然后定义了:<div ng-view></div>,这里可以:查看一下ng-view的api说明, ,ngView是一个指令,主要用于通过已经渲染的模板将当前的服务与主页面(index.html)联结起来.
ngView
is a directive that complements the service by including the rendered template of the current route into the main layou (index.html
) file. Every time the current route changes, the included view changes with it according to the configuration of the$route
service.
用法:
- as element: (This directive can be used as custom element, but be aware of ).
... - as attribute:
... - as CSS class:
...
在本例中用到的是as CSS class,这里ngview是要和$route成队使用的.
2).关于app.js
为了给我们的应用配置路由,我们需要给应用创建一个模块。我们管这个模块叫做phonecat
,并且通过使用config
API,我们请求把$routeProvider
注入到我们的配置函数并且使用.when
API来定义我们的路由规则。
注意到在注入器配置阶段,提供者也可以同时被注入,但是一旦注入器被创建并且开始创建服务实例的时候,他们就不再会被外界所获取到。
我们的路由规则定义如下
- 当URL 映射段为
/phones
时,手机列表视图会被显示出来。为了构造这个视图,AngularJS会使用phone-list.html
模板和PhoneListCtrl
控制器。 - 当URL 映射段为
/phone/:phoneId
时,手机详细信息视图被显示出来。这里:phoneId
是URL的变量部分。为了构造手机详细视图,AngularJS会使用phone-detail.html
模板和PhoneDetailCtrl
控制器。我们重用之前创造过的PhoneListCtrl
控制器,同时我们为手机详细视图添加一个新的PhoneDetailCtrl
控制器,把它存放在app/js/controllers.js
文件里。 $route.otherwise({redirectTo: '/phones'})
语句使得当浏览器地址不能匹配我们任何一个路由规则时,触发重定向到/phones
。
注意到在第二条路由声明中:phoneId
参数的使用。$route
服务使用路由声明/phones/:phoneId
作为一个匹配当前URL的模板。所有以:
符号声明的变量(此处变量为phones
)都会被提取,然后存放在对象中。
3).app/js/controllers.js
这里用的是$http get方法将phones/phones.json的值读取出来;定义phonecatControllers,并配置phonecatControllers,将$routeParams作为变量,将值再赋给$scope.phoneId ,然后显示的routeParams.phoneId;4) phone-detail.html
phone-detail.html中将控制器里phoneId的值显示出来.
5.测试
执行如下命令开始测试:
amosli@amosli-pc:~/develop/angular-phonecat$ npm run protractor
angular-phonecat/test/e2e/scenarios.js
'use strict';/* http://docs.angularjs.org/guide/dev_guide.e2e-testing */describe('PhoneCat App', function() { it('should redirect index.html to index.html#/phones', function() { browser.get('app/index.html'); browser.getLocationAbsUrl().then(function(url) { expect(url.split('#')[1]).toBe('/phones'); }); }); describe('Phone list view', function() { beforeEach(function() { browser.get('app/index.html#/phones'); }); it('should filter the phone list as user types into the search box', function() { var phoneList = element.all(by.repeater('phone in phones')); var query = element(by.model('query')); expect(phoneList.count()).toBe(20); query.sendKeys('nexus'); expect(phoneList.count()).toBe(1); query.clear(); query.sendKeys('motorola'); expect(phoneList.count()).toBe(8); }); it('should be possible to control phone order via the drop down select box', function() { var phoneNameColumn = element.all(by.repeater('phone in phones').column('{ {phone.name}}')); var query = element(by.model('query')); function getNames() { return phoneNameColumn.map(function(elm) { return elm.getText(); }); } query.sendKeys('tablet'); //let's narrow the dataset to make the test assertions shorter expect(getNames()).toEqual([ "Motorola XOOM\u2122 with Wi-Fi", "MOTOROLA XOOM\u2122" ]); element(by.model('orderProp')).findElement(by.css('option[value="name"]')).click(); expect(getNames()).toEqual([ "MOTOROLA XOOM\u2122", "Motorola XOOM\u2122 with Wi-Fi" ]); }); it('should render phone specific links', function() { var query = element(by.model('query')); query.sendKeys('nexus'); element(by.css('.phones li a')).click(); browser.getLocationAbsUrl().then(function(url) { expect(url.split('#')[1]).toBe('/phones/nexus-s'); }); }); }); describe('Phone detail view', function() { beforeEach(function() { browser.get('app/index.html#/phones/nexus-s'); }); it('should display placeholder page with phoneId', function() { expect(element(by.binding('phoneId')).getText()).toBe('nexus-s'); }); });});
测试结果:
Using ChromeDriver directly........Finished in 7.368 seconds5 tests, 8 assertions, 0 failures