我們在學習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的值已經按我們的設想改變了。

上一節中,我們見到了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>
~~~
測試:


由于`{{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>
~~~
刷新頁面,測試:

> 第一次加載時,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。

上圖中,簡單說明了<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('/')`的模板。


## 整理路由表
我們現在需要兩個基本的頁面,第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>:


### 建立模塊
`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的值

## 增加錨點前綴 `!`
前面,我們要求使用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}}">
...
~~~

## 完善依賴注入
我們在`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
- 第十節:完善手機詳情頁
- 第十一節:自定義過濾器
- 第十二節:行為處理
- 第十三節:封裝請求
- 第十四節:應用動畫
- 第十五節:總結
- 第三章:菜譜管理示例
- 第四章:總結