??能夠從頁面的一個視圖跳轉到另外一個視圖,對單頁面應用來講是至關重要的。
??除了用ng-include
指令在視圖中引用多個模板外,更好的做法是將視圖分解成布局和模板視圖,并且根據用戶當前訪問的URL來展示對應的視圖。
??我們會將這些模板分解到視圖中,并在布局模板內進行組裝。AngularJS允許我們在$route
服務的提供者$routeProvider
中通過聲明路由來實現這個功能。
安裝
??從1.2版本開始,AngularJS將ngRoutes
從核心代碼中剝離出來成為獨立的模塊。我們需要安裝并引用它,才能夠在AngularJS應用中正常地使用路由功能。
??可以從http://code.angularjs.org 下載它,然后保存到一個可以在HTML頁面中進行引用的位置。也可以用Bower來安裝,這樣會將它存放到Bower的目錄中。
$bower instal l--save angular-route
??在HTML中,需要在AngularJS之后引用angular-route
:
<script src="js/vendor/angular.js"></script>
<script src="js/vendor/angular-route.js"></script>
??最后,要把ngRoute
模塊在我們的應用中當作依賴加載進來:
angular.module('myApp', ['ngRoute']);
布局模版
??要創建一個布局模板,需要修改HTML以告訴AngularJS把模板渲染到何處。通過將ng-view
指令和路由組合到一起,我們可以精確地指定當前路由所對應的模板在DOM中的渲染位置。
例如:
<header>
<h1>Header</h1>
</header>
<div class="content">
<div ng-view></div>
</div>
<footer>
<h5>Footer</h5>
</footer>
這個例子中,我們將所有需要渲染的內容都放到了<div class="content">
中,而<header>
和<footer>
中的內容在路由改變時不會有任何變化。
ng-view
是由ngRoute
模塊提供的一個特殊指令,它的獨特作用是在HTML中給$route
對應的視圖內容占位。
它會創建自己的作用域并將模板嵌套在內部。
ng-view
是一個優先級為1000的終極指令。AngularJS不會運行同一個元素上的低優先級指令(例如<div ng-view></div>
元素上其他指令都是沒有意義的)。
ngView
指令遵循以下規則。
- 每次觸發$routeChangeSuccess事件,視圖都會更新。
- 如果某個模板同當前的路由相關聯:
- 創建一個新的作用域;
- 移除上一個視圖,同時上一個作用域也會被清除;
- 將新的作用域同當前模板關聯在一起;
- 如果路由中有相關的定義,那么就把對應的控制器同當前作用域關聯起來;
- 觸發$viewContentLoaded事件;
- 如果提供了onload屬性,調用該屬性所指定的函數。
路由
??我們可以使用AngularJS提供的when
和otherwise
兩個方法來定義應用的路由。用config
函數在特定的模塊或應用中定義路由。
angular.module('myApp', []).config(['$routeProvider', function($routeProvider) {
// 在這里定義路由
}]);
??現在,我們可以用when
方法來添加一個特定的路由。這個方法可以接受兩個參數。
下面的例子展示了如何創建一個獨立的路由:
angular.module('myApp', []).config(['$routeProvider', function($routeProvider) {
$routeProvider.when('/', {
templateUrl: 'views/home.html',
controller: 'HomeController'
});
}]);
??第一個參數是路由路徑,這個路徑會與$location.path
進行匹配,$location.path
也就是
當前URL的路徑。如果路徑后面還有其他內容,或使用了雙斜線也可以正常匹配。我們可以在URL中存儲參數,參數需要以冒號開頭(例如:name),用$routeParams
可以讀取這些參數。
第二個參數是配置對象,決定了當第一個參數中的路由能夠匹配時具體做些什么。配置對象中可以進行設置的屬性包括controller
、template
、templateURL
、resolve
、redirectTo
和reloadOnSearch
。
angular.module('myApp', []).
config(['$routeProvider', function($routeProvider) {
$routeProvider
.when('/', {
templateUrl: 'views/home.html',
controller: 'HomeController'
})
.when('/login', {
templateUrl: 'views/login.html',
controller: 'LoginController'
})
.when('/dashboard', {
templateUrl: 'views/dashboard.html',
controller: 'DashboardController',
resolve: {
user: function(SessionService) {
return SessionService.getCurrentUser();
}
}
})
.otherwise({
redirectTo: '/'
});
}]);
1.controller
controller: 'MyController'
// 或者
controller: function($scope) {}
??如果配置對象中設置了controller
屬性,那么這個指定的控制器會與路由所創建的新作用域關聯在一起。如果參數值是字符型,會在模塊中所有注冊過的控制器中查找對應的內容,然后與路由關聯在一起。如果參數值是函數型,這個函數會作為模板中DOM元素的控制器并與模板進行關聯。
2.template
template: '<div><h2>Route</h2></div>'
??AngularJS會將配置對象中的HTML模板渲染到對應的具有ng-view
指令的DOM元素中。
3.templateUrl
templateUrl: 'views/template_name.html'
??應用會根據templateUrl
屬性所指定的路徑通過XHR讀取視圖(或者從$templateCache
中讀取)。如果能夠找到并讀取這個模板,AngularJS會將模板的內容渲染到具有ng-view
指令的DOM元素中。
4.resolve
resolve: {
'data': ['$http', function($http) {
return $http.get('/api').then(
function success(resp) { return response.data; },
function error(reason) { return false; }
);
}];
}
??如果設置了resolve
屬性,AngularJS會將列表中的元素都注入到控制器中。如果這些依賴是promise
對象,它們在控制器加載以及$routeChangeSuccess
被觸發之前,會被resolve
并設置成一個值。
列表對象可以是:
- 鍵,鍵值是會被注入到控制器中的依賴的名字;
- 工廠,即可以是一個服務的名字,也可以是一個返回值,它是會被注入到控制器中的函數或可以被
resolve
的promis
e對象。
??在上面的例子中,resolve
會發送一個$http請求,并將
data的值替換為返回結果的值。列表中的鍵
data`會被注入到控制器中,所以在控制器中可以使用它。
5.redirectTo
redirectTo: '/home'
// 或者
redirectTo: function(route,path,search)
??如果redirectTo屬性的值是一個字符串,那么路徑會被替換成這個值,并根據這個目標路徑觸發路由變化。
??如果redirectTo屬性的值是一個函數,那么路徑會被替換成函數的返回值,并根據這個目標路徑觸發路由變化。
??如果redirectTo屬性的值是一個函數,AngularJS會在調用它時傳入下面三個參數中:
??(1) 從當前路徑中提取出的路由參數;
??(2) 當前路徑;
??(3) 當前URL中的查詢串。
6.reloadOnSearch
??如果reloadOnSearch
選項被設置為true(默認),當$location.search()
發生變化時會重新加載路由。如果設置為false,那么當URL中的查詢串部分發生變化時就不會重新加載路由。
??下面的例子中設置了兩個路由:一個首頁路由和一個收件箱路由,同時首頁路由被設置成默認路由。
angular.module('MyApp', []).config(['$routeProvider', function($routeProvider) {
$routeProvider.when('/', {
controller: 'HomeController',
templateUrl: 'views/home.html'
})
.when('/inbox/:name', {
controller: 'InboxController',
templateUrl: 'views/inbox.html'
})
.otherwise({redirectTo: '/'});
}]);
??otherwise
方法會在沒有任何路由匹配時被調用,我們用它設置了一個默認跳轉到'/'
路徑的路由。
??當瀏覽器加載AngularJS應用時,會將URL設置成默認路由所指向的路徑。除非我們在瀏覽器中加載不同的URL,否則默認會使用'/'
路由。
$routeParams
??如果我們在路由參數的前面加上:,AngularJS就會把它解析出來并傳遞給$routeParams
。
例如,如果我們設置下面這樣的路由:
$routeProvider
.when('/inbox/:name', {
controller: 'InboxController',
templateUrl: 'views/inbox.html'
});
??AngularJS會在$routeParams
中添加一個名為name
的鍵,它的值會被設置為加載進來的URL中的值。
??如果瀏覽器加載/inbox/all
這個URL,那么$routeParams
對象看起來會是這樣:{ name: 'all' }
。需要注意,如果想要在控制器中訪問這些變量,需要把$routeParams
注入進控制器:
app.controller('InboxController', function($scope,$routeParams) {
// 在這里訪問$routeParams
});
$location 服務
??AngularJS提供了一個服務用以解析地址欄中的URL,并讓你可以訪問應用當前路徑所對應的路由。它同樣提供了修改路徑和處理各種形式導航的能力。
??$location
服務對JS中的window.location
對象的API進行了更優雅地封裝,并且和AngularJS集成在一起。
??當應用需要在內部進行跳轉時是使用$location
服務的最佳場景,比如當用戶注冊后、修改或者登錄后進行的跳轉。
??$location
服務沒有刷新整個頁面的能力。如果需要刷新整個頁面,需要使用$window.location
對象(window.location的一個接口)。
1.path()
path()用來獲取頁面當前的路徑:
$location.path(); // 返回當前路徑
修改當前路徑并跳轉到應用中的另一個URL:
$location.path('/'); // 把路徑修改為'/'路由
path()方法直接和HTML5的歷史API進行交互,所以用戶可通過點擊后退按鈕退回到上一個頁面。
2. replace()
如果你希望跳轉后用戶不能點擊后退按鈕(對于登錄之后的跳轉這種發生在某個跳轉之后的再次跳轉很有用),AngularJS提供了replace()方法來實現這個功能:
$location.path('/home');
$location.replace();
// 或者
$location.path('/home').replace();
3. absUrl()
absUrl()方法用來獲取編碼后的完整URL:
$location.absUrl()
4. hash()
hash()方法用來獲取URL中的hash片段:
$location.hash(); // 返回當前的hash片段
5. host()
host()方法用來獲取URL中的主機:
$location.host();// 當前URL的主機
6. port()
port()方法用來獲取URL中的端口號:
$location.port();// 當前URL的端口
7. protocol()
protocol()方法用來獲取URL中的協議:
$location.protocol();// 當前URL的協議
8. search()
search()方法用來獲取URL中的查詢串:
$location.search();
我們可以向這個方法中傳入新的查詢參數,來修改URL中的查詢串部分:
// 用對象設置查詢
$location.search({name: 'Ari', username: 'auser'});
// 用字符串設置查詢
$location.search('name=Ari&username=auser');
search方法可以接受兩個參數。
- search(可選,字符串或對象)
這個參數代表新的查詢參數。hash對象的值可以是數組。 - paramValue(可選,字符串)
如果search參數的類型是字符串,那么paramValue會做為該參數的值覆蓋URL當中的對應值。如果paramValue的值是null,對應的參數會被移除掉。
9. url()
url()方法用來獲取當前頁面的URL:
$location.url(); // 該URL的字符串
如果調用url()方法時傳了參數,會設置并修改當前的URL,這會同時修改URL中的路徑、查詢串和hash,并返回$location。
// 設置新的URL
$location.url('/home?name=Ari#hashthing');
url()方法可以接受兩個參數:
- url(可選,字符串)
新的URL的基礎的前綴。 - replace(可選,字符串)
想要修改成的路徑。
路由模式
??不同的路由模式在瀏覽器的地址欄中會以不同的URL格式呈現。$location
服務默認會使用標簽模式來進行路由。
??路由模式決定你的站點的URL長成什么樣子。
標簽模式
??標簽(hashbang)是AngularJS用來同你的應用內部進行鏈接的技巧。標簽模式是HTML5模式的降級方案,URL路徑會以#符號開頭。標簽模式不需要重寫<a href=""></a>
標簽,也不需要任何服務器端的支持。如果沒有進行額外的指定,AngularJS將默認使用標簽模式。
使用標簽模式的URL看起來是這樣的:
http://yoursite.com/#!/inbox/all
??如果要顯式指定配置并使用標簽模式,需要在應用模塊的config
函數中進行配置:
angular.module('myApp', ['ngRoute'])
.config(['$locationProvider', function($locationProvider) {
$locationProvider.html5Mode(false);
}]);
我們還可以配置hashPrefix
,也就是標簽模式下標簽默認的前綴!
符號。這個前綴也是AngularJS在比較老的瀏覽器中降級機制的一部分。這個符號是可以配置的:
angular.module('myApp', ['ngRoute'])
.config(['$locationProvider', function($locationProvider) {
$locationProvider.html5Mode(false);
$locationProvider.hashPrefix('!');
}]);
HTML5 模式
??AngularJS支持的另外一種路由模式是html5模式。在這個模式中,URL看起來和普通的URL
一樣。例如,同樣的路由在HTML5模式中看起來是這樣的:http://yoursite.com/inbox/all
。
??在AngularJS內部,$location
服務通過HTML5歷史API讓應用能夠使用普通的URL路徑來路由。當瀏覽器不支持HTML5歷史API時,$location
服務會自動使用標簽模式的URL作為替代方案。
??$location
服務還有一個有趣的功能,當一個支持HTML5歷史API的現代瀏覽器加載了一個帶標簽的URL時,它會為用戶重寫這個URL。
??在HTML5模式中,AngularJS會負責重寫<a href=""></a>
中的鏈接。也就是說AngularJS會
根據瀏覽器的能力在編譯時決定是否要重寫href=""
中的鏈接。
??例如<a href="/person/42?all=true">Person</a>
這個標簽,在老式瀏覽器中會被重寫成標簽模式的URL:/index.html#!/person/42?all=true
。但在現代瀏覽器中會URL會保持本來的樣子。
??后端服務器也需要支持URL重寫,服務器需要確保所有請求都返回index.html
,以支持HTML5模式。這樣才能確保由AngularJS應用來處理路由。
??當在HTML5模式的AngularJS中寫鏈接時,永遠都不要使用相對路徑。如果你的應用是在根路徑中加載的,這不會有什么問題,但如果是在其他路徑中,AngularJS應用就無法正確處理路由了。
另一個選擇是在HTML文檔的HEAD中用<base>
標簽來指定應用的基礎URL:
<base href="/base/url" />
路由事件
??$route
服務在路由過程中的每個階段都會觸發不同的事件,可以為這些不同的路由事件設置監聽器并做出響應。這個功能對于控制不同的路由事件,以及探測用戶的登錄和授權狀態等場景是非常有用的。
??我們需要給路由設置事件監聽器,用$rootScope
來監聽這些事件。
1. $routeChangeStart
??AngularJS在路由變化之前會廣播$routeChangeStart
事件。在這一步中,路由服務會開始加載路由變化所需要的所有依賴,并且模板和resolve
鍵中的promise
也會被resolve
。
angular.module('myApp', [])
.run(['$rootScope', '$location', function($rootScope, $location) {
$rootScope.$on('$routeChangeStart', function(evt, next, current) {
});
}]);
$routeChangeStart事件帶有兩個參數:
- 將要導航到的下一個URL;
- 路由變化前的URL。
2. $routeChangeSuccess
??AngularJS會在路由的依賴被加載后廣播$routeChangeSuccess
事件。
angular.module('myApp', [])
.run(['$rootScope', '$location', function($rootScope, $location) {
$rootScope.$on('$routeChangeSuccess', function(evt, next, previous) {
});
}]);
$routeChangeStart事件帶有三個參數:
- 原始的AngularJS evt對象;
- 用戶當前所處的路由;
- 上一個路由(如果當前是第一個路由,則為undefined)。
3. $routeChangeError
??AngularJS會在任何一個promise
被拒絕或者失敗時廣播$routeChangeError
事件。
angular.module('myApp', [])
.run(function($rootScope, $location) {
$rootScope.$on('$routeChangeError', function(current, previous, rejection) {
});
});
$routeChangeError事件有三個參數:
- 當前路由的信息;
- 上一個路由的信息;
- 被拒絕的promise的錯誤信息。
4. $routeUpdate
??AngularJS在reloadOnSearch
屬性被設置為false的情況下,重新使用某個控制器的實例時,會廣播$routeUpdate
事件。
關于搜索引擎索引
??Web爬蟲對于JS的胖客戶端應用無能為力。為了在應用的運行過程中給爬蟲提供支持,我們需要在頭部添加meta標簽。這個元標記會讓爬蟲請求一個帶有空的轉義片段參數的鏈接,服務器根據請求返回對應的HTML代碼片段。
<meta name="fragment" content="!"/>
更多關于路由的內容
頁面重新加載
??$location
服務不會重新加載整個頁面,它只會單純地改變URL。如果我們想重新加載整個頁面,需要用$window
服務來設置地址。
$window.location.href = "/reload/page";
異步的地址變化
??如果我們想要在作用域的生命周期外使用$location
服務,必須用$apply
函數將變化拋到應用外部。因為$location
服務是基于$digest
來驅動瀏覽器的地址變化,以使路由事件正常工作的。