在这步,你将学习创建布局,怎么通过增加路由,使用Angular模型叫做‘ngRoute’, 创建一个应用拥有更多的视图。
当你导航到 app/index.html,你会被指向 app/index.html/#/phones
,手机列表会出现在浏览器中。
当你点击手机链接,URL会更改到那个手机特有的,手机的详细页面会显示。
工作空间重置介绍 重置你的工作区间到第七步
git checkout -f step-7
刷新你的浏览器,或者在线上检出这一步:第七步例子。 大部分重要的修改都列在下面,你在 GitHub上能看全部不同。
在这一步,Angular中 ngRoute
模块的路由功能被增加。他是从核心Angular框架分布式中分离。 我们使用 Bower去安装客户端依赖,这步将更新 bower.json
配置文件,包括新增的依赖:
{ "name": "angular-phonecat", "description": "A starter project for AngularJS", "version": "0.0.0", "homepage": "https://github.com/angular/angular-phonecat", "license": "MIT", "private": true, "dependencies": { "angular": "~1.3.0", "angular-mocks": "~1.3.0", "jquery": "2.1.1", "bootstrap": "~3.1.1", "angular-route": "~1.3.0" } }
新的依赖 "angular-route": "~1.3.0"
告诉bower去安装兼容于1.3.x版本的angular-route组件,我们只要告诉bower下面并安装依赖。 如果你安装了全局的bower,你就可以运行 bower install
,但在这个项目中,我们已经预配置npm去运行bower给我们安装。
npm install
我们的应用慢慢成长也变得更复杂。在第七步前,应用提供单个的页面给用户(列出所有的电话),所有的模板代码位于index.html文件中,下一步中建设应用是增加一个视图,用来展现我们列表中每个设备的详细信息。 为了增加一个详细的视图,我们继承 index.html
文件,包含所有视图的模板代码,但是麻烦很快就来了。相反,我们要转换index.html模板成我们称为的布局模板。这个模板是我们应用所有视图公共的。依赖当前路由,其他“局部模板”被包含到这个布局模板中——视图正确显示给用户。 Angular应用的路由定义通过由 $route服务提供的 $routeProvider提供,这个服务让控制器、视图模板和当前浏览器中URL位置很容易的连在一起。使用这个特征,我们能够实现 深层连接,这样我们能利用浏览器的历史(返回与前进导航)与书签。
如你所 注意的, 依赖注入(DI)是AngularJS的核心,所以你懂得一些关于他怎么工作是很重要的。 当应用启动,Angular创建一个注入器,他将用来注入所有服务,这是你的应用必要的。注入器自己不知道 $http
与$route服务是怎么工作的,实际上,他甚至不知道这些服务的存在,直到他被模板正确的定义。 注入器只会做下面的步骤:
读取你指定的模块定义。
注册模块中定义的所有提供者。
当被寻问加载,注入指定的函数和任何需要的依赖,他会通过提供者延迟加载。
提供者是提供(创建)服务实例和暴露APIs配置的对象,他能用来控制创建和服务运行时的行为,在例子中,$route服务, $routeProvider
暴露APIs,允许你定义你的应用路由。 注意:提供者只能注入config
函数中,这样你不能注入$routePRovider
到PhoneListCtrl
中。 Angular模型解决了应用中移走全局状态的问题和提供了配置注入器的方式,相对于AMD或requie.js模块,Angular模块不会尝试解决脚本的读取顺序与延迟读取,这些目的都是独立的,都能所有的模块能并排生存,完成他们的目的。 想增加你对AngularDI的了解,看 了解依赖注入。
$route服务经常与NgView联合使用,ngView命令的角色是把当前的视图包含到布局模板中,这让他完美的适配到我们index.html的模板中。 注意:从AngularJS1.2版本起,ngRoute
在他自己的模块中,必须用额外的angular-route.js
文件加载,我们可以通过Bower下载。 app/index.html:
<!doctype html> <html lang="en" ng-app="phonecatApp"> <head> ... <script src="bower_components/angular/angular.js"></script> <script src="bower_components/angular-route/angular-route.js"></script> <script src="js/app.js"></script> <script src="js/controllers.js"></script> </head> <body> <div ng-view></div> </body> </html>
在我们的index文件,我们增加了两个新 <sript>
标签,去加载额外的JavaScript到我们的应用。
angular-route.js : 定义Angular的ngRoute模板,他提供路由给我们。
app.js : 这个文件现在持有我们的应用的root模块。
注意我们从 index.html
中移走了大部分代码,并用有 ng-view
属性的div单行替换。我们移走的代码到 phone-list.html
模板:
<div class="container-fluid"> <div class="row"> <div class="col-md-2"> <!--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="col-md-10"> <!--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>
我们也增加了一个留白模板给我们的手机详细视图。 app/partials/phone-detail.html:
TBD: detail view for <span>{{phoneId}}</span>
注意我们使用了在 PhoneDetailCtrl
控制器中定义的phoneId表达式。
为了提高我们应用的组织性,我们使用Angular的ngRoute模块,我们从控制器中移到了他们自己的模块中。(如下面的显示) 我们添加了 angualr-route.js
到 index.html
,在controllers.js中创建了新的phonecatControllers模块,那不是我们使用他们的代码所有需要做全部的。然而,我们给我们应用也增加了依赖模块,通过列出phonecatApp中依赖的两个模块,我们能使用他们提供的命令和服务。 app/js/app.js:
var phonecatApp = angular.module('phonecatApp', [ 'ngRoute', 'phonecatControllers' ]); ...
注意第二个参数通过 angular.module,['ngRoute', 'phonecatControllers']
,这个数据列出phonecat依赖的模块。
... 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' }); }]);
使用 phonecatApp.config()
方法,我们请求 $routeProvider
去注入到我们的配置函数,使用$routeProvider方法定义给我们的路由。 我们的应用定义如下:
when('/phones')
: 当URL哈希值是/phones
手机列表视图将显示,为了构成这个视图,Angular将会使用phone-list.html
模板和PhoneListCtrl
控制器。
when('/phones/:phoneId')
: 当URL哈希值配置'/phones/:phoneId
'手机详细将会被展示,:phoneId是URL中部分变量,为了构建手机详细视图,Angular将会使用phone-detail.html
模板和PhoneDetailCtrl
控制器。
otherwise({redirectTo: '/phones'}):
当浏览器地址不匹配任何我们的路由时,就触发重定向到/phones。
我们重用了我们在前面步骤中构建的PhoneListCtrl控制器,也给手机详细视图增加了一个新的,空的 PhoneDetailCtrl
控制器到 app/js/controllers.js
。 注意在第二个路由定义中,我们使用了:phoneId参数。 $route
服务像模板一样使用路由定义' /phones/:phoneId
',匹配当前对应的URL,所有变量使用:符号都会提取到 $routeParams
对象中。
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; }]);
注意,我们创建了一个叫 phonecatControllers
的模块,对于小AngularJS应用,如果只有少量控制器,通常只给所有的控制器创造一个模块。当应用增加,完全可以重构你的代码,增加模块,对于一个大型应用,你可能给你应用大特征创建分离的模块。 因为我们的例子应用相对小,我们只增加我们所有控制器到 phonecatControllers
模块。
去自动验证所有都连接好,我们写端对端测试,指向可能的URL地址,验证当前视图已经渲染。
... 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'); }); ... 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'); }); });
你可以运行 npm run protractor
去看测试运行。
尝试增加一个 {{orderProp}}
绑定给 index.html
,尽管你在手机列表中,但是会发现没有发生什么事,这是因为 orderProps
模型只对 PhoneListCtrl
管理的scope中可见。关联在 <div ng-view>
元素上,如果你增加相同的绑定到 phone-list.html
模型,这个绑定会如你所愿工作。
使用路由启动手机列表已经实现,我们现在去 第8步,实现手机列表详细视图。 原文地址: https://docs.angularjs.org/tutorial/step_07