我们在学习html的时候已经学习到,通过a标签来实现页面间的跳转,使用a标签的锚点来实现在页面内的导航。在angularjs的世界中,所有类似的跳转都是通过“锚点”来完成的。没错,这就是通过锚点来实现单页面(SPA)的。单页面大幅提升了用户的使用感受,真正的实现了一次加载,即使在网络不好的情况下,也会出现加载的loading动画,使等待并不显得那么枯燥。
> 关于锚点,这篇文章不错: http://www.zhangxinxu.com/wordpress/2013/08/url-anchor-html-%E9%94%9A%E7%82%B9%E5%AE%9A%E4%BD%8D%E6%9C%BA%E5%88%B6-%E5%BA%94%E7%94%A8-%E9%97%AE%E9%A2%98/
本节中,我们来共同学习下angularjs是如何使用锚点来进行页面的『跳转』的。
# 增加锚点信息
我们修改下模板,来增加a标签,并在a标签上写入锚点信息。
`yun-zhi/phone-list.template.html`
~~~
<ul class="phones">
<li ng-repeat="phone in $ctrl.phones | filter:$ctrl.query | orderBy:$ctrl.orderProp" class="thumbnail">
<a ng-href="#/phones/{{phone.id}}" class="thumb">
<img ng-src="{{phone.imageUrl}}" alt="{{phone.name}}" class="thumb"/>
</a>
<a ng-href="#/phones/{{phone.id}}">{{phone.name}}</a>
<p>{{phone.snippet}}</p>
</li>
</ul>
~~~
点击图片或是标题的时候,发现URL的值已经按我们的设想改变了。
![](https://box.kancloud.cn/2016-07-29_579afa23b1ffc.png)
上一节中,我们见到了ng-src,还记得为什么我们要这么用吧?这节中,我们又迎来了ng-href。
* * * * *
*选读开始*
官方是这么解释的。
**Using Angular markup like {{hash}} in an href attribute will make the link go to the wrong URL if the user clicks it before Angular has a chance to replace the {{hash}} markup with its value. Until Angular replaces the markup the link will be broken and will most likely return a 404 error. The ngHref directive solves this problem.**
上面大概的意思是说:如果使用了href,那么当页面还没有被angluarjs渲染完毕时,用户就进行了a标签的点击的话,就可以会得到一个错误的链接,而使用ng-href可以规避这个问题。
为了验证官方的正确性,我们来做个测试:
`test.html`
angularjs正确渲染
~~~
<!DOCTYPE html>
<html lang="en" ng-app="test">
<head>
<meta charset="UTF-8">
<title>test</title>
<script src="bower_components/angular/angular.js"></script>
</head>
<body ng-controller="ctrl">
<a href="{{google}}">google</a>
</body>
<script type="text/javascript">
angular.module('test', [])
.controller('ctrl', function($scope){
$scope.google = 'https://www.google.com/ncr';
})
</script>
</html>
~~~
去除angularjs渲染的代码:
~~~
<!DOCTYPE html>
<html lang="en" ng-app="test">
<head>
<meta charset="UTF-8">
<title>test</title>
<script src="bower_components/angular/angular.js"></script>
</head>
<body ng-controller="ctrl">
<a href="{{google}}">google</a>
</body>
<script type="text/javascript">
// angular.module('test', [])
// .controller('ctrl', function($scope){
// $scope.google = 'https://www.google.com/ncr';
// })
</script>
</html>
~~~
测试:
![](https://box.kancloud.cn/2016-07-29_579afa23d49e9.png)
![](https://box.kancloud.cn/2016-07-29_579afa23eceab.png)
由于`{{google}}`是一个错误的地址,所以404了。
但如果换成ng-href就不样了。用同样的方法,把href换成ng-href,自己试一下看看效果吧。
*选读结束*
* * * * *
当然了,上面的原因了解一下就可以了。你需要知道的是:我们要使用ng-href而不是href。
#增加路由
点击完a标签,我们当然希望它能够跳转到我们想的手机详情页面,也不是只改变URL。
下面,我们使用一个新的模块`ngRoute`来解决这个问题。我们在前面学习了,如果在当前模块中想使用yunZhi模块,那么需要将yunZhi模块注入到当前模块。在angularjs中,我们把这种方式称为依赖注入。现在我们要使用一个新的叫做ngRoute的模块,当然也需要用同样的方法将ngRoute注入到当前模块中。
yunZhi模块是我们写的,直接放置在了yun-zhi文件夹中,然后进行行引用。ngRouter模块是别人(官方)写的,那么在使用之前,我们还需要的下载这个模块的JS文件。然后在index.html进行引用,再然后在angular.moudle中进行依赖注入。
## 下载JS文件
ngRoute全称为:angular-route,和下载angularjs及bootstrap一样,我们可以使用`bower intall angular-route#1.5.7`来完成下载。
> angular-router的版本要和angularjs的版本一致。
## 引用
下载后,我们下一步就是要把这个JS文件引用到我们的index.html中。
~~~
<script src="bower_components/angular/angular.js"></script>
<script src="bower_components/angular-route/angular-route.js"></script>
<script src="app.moudle.js"></script>
~~~
刷新页面,测试:
![](https://box.kancloud.cn/2016-07-29_579afa24133ac.png)
> 第一次加载时,status应该为200。非首次加载时,浏览器会将当前缓存中的文件信息(Last-Modified及If-None-Match)发送给服务器,服务器跟据这两个值来判断浏览器中的缓存文件是否为最新的,如果是最新的,则会发送304。如果不是,则会重新传送用户的请求文件。
【注意】
angular-route是angularjs的一个模块,所以在引用顺序上,要先引用angularjs,然后再引用angular-route,否则,将会产生错误。
> angular-route官方文档: https://docs.angularjs.org/api/ngRoute
## 依赖注入
下面,我们将angular-route注入到我们的项目模块
`app.moudle.js`
~~~
// 定义模块
var phonecatApp = angular.module('phonecatApp', ['yunZhi', 'ngRoute']);
~~~
有了前面注入yunZhi的经验,这个ngRoute写起来就很轻松了。
刷新页面,看控制台没报出任何信息。注入成功。
## 增加路由
路由这个名称,可能我们开始摸不清门路。如果你读过ThinkPHP的开发手册,相信对于这个名词一定不陌生。
简单来说,路由会根据当前的URL来触发软件的某个特定的功能。在angularjs中,也是如此。
### ng-view
在ngRoute模块中,有一个ng-view组件。ngRoute会依据不同的URL请求,将待定的内容放置于ng-view中。
为此,我们在index.html增加一个ng-view标签,用来显示ngRoute按不同的URL为我们生成的HTML。
![](https://box.kancloud.cn/2016-07-29_579afa243588b.png)
上图中,简单说明了<phone-list>标签被移除后,再通过ng-view载入的过程。
### 增加路由配置
在配置路由以前,我们还回想一下`phone-list`组件:
~~~
angular.
module('yunZhi').
component('phoneList', {
templateUrl: 'yun-zhi/phone-list.template.html',
controller: ['$http',
function PhoneListController(param1) {
var self = this;
self.orderProp = 'age';
param1.get('phones/phones.json').then(function(response) {
self.phones = response.data;
});
}
]
});
~~~
模块配置调用的是component(),路由的配置调用的是config()
我们新建一个`app.config.js`,并且调用config()来进行路由的配置.
1. 初始化
`app.config.js`
~~~
angular.
module('phonecatApp').
config(['$routeProvider', function($routeProvider){}]);
~~~
2.增加配置
`app.config.js'
~~~
angular.
module('phonecatApp').
config(['$routeProvider',
function($routeProvider) {
$routeProvider.
when('/', {template:'hello yunzhi'}).
when('/yunzhi', {template:'hello dreamer'}).
otherwise('/');
}
]);
~~~
$routeRrovider提供两个方法.
`when('/', {template:'hello yunzhi'})`当锚点为/时,输出'hello yunzhi'
`.otherwise('/')`, 当URL不是`/`, 也不是`/yunzhi`时。则调用`when('/')`的模板。
![](https://box.kancloud.cn/2016-07-29_579afa245b6f0.png)
![](https://box.kancloud.cn/2016-07-29_579afa2475012.png)
## 整理路由表
我们现在需要两个基本的页面,第1个是手机列表页,不需要传入任何参数。第2个是手机详情页,需要传入手机ID以确定显示哪个手机的详情。
手机列表页 url -> /phones
手机详情页 url -> /phones/:phoneId
phoneId为手机关键字信息,类似于我们以前的GET信息。
按整理的路由表,重写config
`app.config.js`
~~~
angular.
module('phonecatApp').
config(['$routeProvider',
function($routeProvider) {
$routeProvider.
when('/phones', {template:'<phone-list></phone-list>'}).
when('/phones/:phoneId', {template:'hello dreamer'}).
otherwise('/phones');
}
]);
~~~
同时,我们在模板的属性中,恢复为<phone-list>:
![](https://box.kancloud.cn/2016-07-29_579afa24940d5.png)
![](https://box.kancloud.cn/2016-07-29_579afa24b13a0.png)
### 建立模块
`yun-zhi/phone-detail.component.js`
~~~
angular.
module('yunZhi').
component('phoneDetail', {
template: 'id:{{$ctrl.phoneId}}',
controller: ['$routeParams',
function PhoneListController($routeParams) {
// 使用$routeParam获取phoneId
// $routeParam存在于angular-route模块中
this.phoneId = $routeParams.phoneId;
}
]
});
~~~
### 引入js文件
~~~
<script src="yun-zhi/hello-yunzhi.component.js"></script>
<script src="yun-zhi/phone-detail.component.js"></script>
</head>
~~~
这里,我们使用$routeParams来获取phoneId的值
![](https://box.kancloud.cn/2016-07-29_579b077350890.png)
## 增加锚点前缀 `!`
前面,我们要求使用ng-src和ng-href。在这,我们在加一个前缀`!`。比如以前这样使用`#/lists`,增加前缀后我们这样使用`#!/list`至于为什么要这么用。现在我们还不需要知道原因。
官方文档如是说:
> We also used $locationProvider.hashPrefix() to set the hash-prefix to !. This prefix will appear in the links to our client-side routes, right after the hash (#) symbol and before the actual path (e.g. index.html#!/some/path).
Setting a prefix is not necessary, but it is considered a good practice (for reasons that are outside the scope of this tutorial). ! is the most commonly used prefix.
## 使用$locationProvider增加!前缀。
修改配置信息。
> 所有 注入模块 的配置信息,都需要在.config()中进行定义。比如,我们在config()中定义, ngRoute模块的配置信息。
`app.config.js`
~~~
angular.
module('phonecatApp').
config(['$routeProvider', '$locationProvider',
function($routeProvider, $locationProvider) {
$locationProvider.hashPrefix('!');
$routeProvider.
when('/phones', {template:'<phone-list></phone-list>'}).
when('/phones/:phoneId', {template:'<phone-detail></phone-detail>'}).
otherwise('/phones');
}
]);
~~~
定义完前缀后,我们就需要使用带有这个前缀的信息来定义锚点了。
修改锚点信息
`phone-list.template.html`
~~~
...
<a ng-href="#!/phones/{{phone.id}}" class="thumb">
...
<a ng-href="#!/phones/{{phone.id}}">
...
~~~
![](https://box.kancloud.cn/2016-08-03_57a1591f9a6c0.png)
## 完善依赖注入
我们在`yun-zhi/phone-detail.component.js`中使用了`$routeParams`对象,此对象位于`angular-route`中。在本节上,使其生效的方法是:在`app.moudle.js`注入了`ng-route`。但如果想将yun-zhi做为一个单独的模块发布的话,则需要我们在yun-zhi模块中解决这个依赖。
`yun-zhi.moudle.js`
~~~
- angular.module('yunZhi', []);
+ angular.module('yunZhi', ['ngRoute']);
~~~
这样,我们便在模块中注入了这个`ngRoute`,可以将模块代码打包发布后供其它应用使用了。
有人说,如果这样做,我们在app.moudle.js中注入了一次,在这再注入一次,相同的操作会不会被执行两次,从而造了两个相同的轮子。angularjs当然想到了这点,它的解决方案叫做:惰性加载。简单的说是,不管你注入多少次,我们只会在需要使用的时候加载一次。
* * * * *
`phone-list.template.html`
~~~
<div class="container-fluid">
<div class="row">
<div class="col-md-2">
<p>
Search:
<input ng-model="$ctrl.query" />
</p>
<p>
Sort By:
<select ng-model="$ctrl.orderProp">
<option value="name">Alphabetical</option>
<option value="age">Newest</option>
</select>
</p>
</div>
<div class="col-md-10">
<!--Body content-->
<ul class="phones">
<li ng-repeat="phone in $ctrl.phones | filter:$ctrl.query | orderBy:$ctrl.orderProp" class="thumbnail">
<a ng-href="#!/phones/{{phone.id}}" class="thumb">
<img ng-src="{{phone.imageUrl}}" alt="{{phone.name}}" class="thumb"/>
</a>
<a ng-href="#!/phones/{{phone.id}}">{{phone.name}}</a>
<p>{{phone.snippet}}</p>
</li>
</ul>
</div>
</div>
</div>
~~~
`phone-detail.component.js`
~~~
angular.
module('yunZhi').
component('phoneDetail', {
template: 'id:{{$ctrl.phoneId}}',
controller: ['$routeParams',
function PhoneListController($routeParams) {
// 使用$routeParam获取phoneId
// $routeParam存在于angular-route模块中
this.phoneId = $routeParams.phoneId;
}
]
});
~~~
- 前言
- 第一章:准备知识
- 第一节:GIT
- 第二节:Node.js
- 第三节:http-server
- 第四节:bower
- 第五节:firefox+chrome
- 第二章:官方示例教程
- 第零节:Hello Yunzhier
- 第一节:静态模板
- 第二节:MVC
- 回调函数
- 第三节:组件
- 第四节:重构组件
- 2.4.1 调用组件
- 2.4.2 规划目录结构
- 2.4.3 剥离V层
- 2.4.4 大话测试
- 第五节:循环过滤器
- 第六节:双向数据绑定
- 第七节:XHR与依赖注入
- 第八节:添加缩略图
- 第九节:模拟页面跳转
- 2.9.1 使用bower
- 2.9.2 使用grunt
- 第十节:完善手机详情页
- 第十一节:自定义过滤器
- 第十二节:行为处理
- 第十三节:封装请求
- 第十四节:应用动画
- 第十五节:总结
- 第三章:菜谱管理示例
- 第四章:总结