之前已經介紹了$location服務的基本用法:angular學習筆記(三十一)-$location(1).
這篇是上一篇的進階,介紹$location的配置,相容各版本瀏覽器,等.
*注意,這裡介紹的是基於angular-1.3.2版本的,低版本的$location可能會有問題.
hashbang模式和history api建立單頁應用
首先,$location是用在單頁應用裡的...(廢話,angular就是用在單頁的)...所以,$location處理的是url改變,但是不重新整理頁面的情況.那麼我們知道,不重新整理頁面但是請求ajax改變url,需要存入歷史記錄.這樣的話,需要使用html5的history api,但是對於不支援history api的瀏覽器(也就是ie8,9吧,反正ie7angular本來就不支援,而且看了花瓣網,它在ie7,8,也沒有使用單頁,而是新開連結),則需要使用hashbang模式.
什麼叫hashbang模式?在網上查閱了很多也沒有查到具體的說明.只能按照自己理解的來總結:
比如一個url: http://localhost:801/$location/index.html,這個頁面,在垃圾瀏覽器裡,需要實現傳送ajax,改變url,但是不重新整理頁面,並且可以使用後退前進按鈕,怎麼做呢? 就在url後面使用'#'加一個識別符號'!',再加上路徑,引數,雜湊值等...這樣,因為使用了'#',所以頁面不會重新整理,而url也改變了,可以存入歷史記錄.其中'#'代表了hash,'!'代表了bang,所以這種模式被稱為hashbang模式.注意,'!'不是固定的,可以是任意的識別符號,也可以為空的.而且,當我重新整理http://localhost:801/$location/index.html#!/foo?name=code_bunny#bunny時,可以訪問到http://localhost:801/$location/index.html.
這就是我自己總結的傳統的hashbang模式.而使用HTML5 history api,則不需要'#'和標示符.只需要直接在url後面加上路徑,引數,雜湊值,等...但是當我重新整理http://localhost:801/$location/index.html/foo?name=code_bunny#bunny時,是不能訪問到http://localhost:801/$location/index.html頁面的,需要服務端進行配置,使應用能夠接受來自http://localhost:801/$location/index.html/foo?name=code_bunny#bunny的請求.
介紹完了hashbang和history api,接下來來看下angular是怎麼處理它們的.
配置$location服務
可以看到,在 angular學習筆記(三十一)-$location(1)這篇文章裡,所有例子的url都是帶有#的,(bang識別符號為空),無論在任何瀏覽器裡,它都是這樣的,而沒有使用history api. 如果需要使用history api,或者配置bang識別符號,可以對$location服務進行配置:
var locationApp = angular.module('locationApp',[]);
locationApp.config(function($locationProvider){
$locationProvider.html5Mode(true).hashPrefix('!');
});
$locationProvider有兩個方法可以配置:
$locationProvider.html5Mode(boolean || obj)
1.$locationProvider.html5Mode(true): 開啟html5的history api
2.$locationProvider.html5Mode(false): 關閉html5的history api
3.$locationProvider.html5Mode({enabled:true}): 同1
4.$locationProvider.html5Mode({enabled:false}): 同2(預設配置)
5.$locationProvider.html5Mode({requireBase:true}): 設定為'需要定義base href'標籤(後面會講到base href標籤)
6.$locationProvider.html5Mode({requireBase:false}): 設定為'不需要定義base href'標籤(後面會講到base href標籤)
$locationProvider.hashPrefix('string')
設定bang識別符號.不設定的話,預設為空
base href屬性的定義
在介紹hashbang和histroy api的時候,我把url分成了兩部分顏色顯示,第一部分是淺綠色的,第二部分是深綠色的.淺綠色的是固定不變的,而深綠色的,是可以改變的.也就是$location.url()
那麼瀏覽器是如何知道固定的是什麼的呢? 就是通過設定base href屬性:
在head標籤裡新增如下標籤:
<base href="/$location/base/">
這樣,瀏覽器就知道,不變的部分是 http://localhost:801/$location/base/.注意,一定要有'/'開頭,'/'結尾,否則在垃圾瀏覽器裡會有問題.因為它不會自己給它加上'/',但如果你定義的base href是'/$location/base/index.html',那是可以的.
另外,定義了這個base href以後,頁面裡所有的js,css,都是這個base href的相對路徑.
前面提到了$locationProvider.html5Mode的引數的requireBase屬性,就是用來定義是否頁面中一定要定義base href標籤的.
如果不定義,它會自己把頁面的根目錄作為固定部分,比如 http://localhost:801/$location/base/index.html, 它會認為固定部分是http://localhost:801
總結一下,base href的值,應該是域名部分後面的整個路徑部分,以/開始,/結尾,注意一定要加這個屬性!!!實際工作中很容易忘記加它導致路由不生效也不報錯.被坑過很多次了!!!
a連結的跳轉
頁面的url改變,肯定是點選了某些元素,最常見的自然是a連結,a連結本來的作用是跳轉頁面,但在單頁應用中,我們只需要讓它改變url後面的$location.url()部分,而不重新整理頁面.所以,a連結可以這麼寫:
<a href="some1?foo=bar">/some1?foo=bar</a>
<a href="some2?foo=bar#myhash">/some2?foo=bar#myhash</a>
angular會自動處理瀏覽器的相容問題.假設點選第一個連結:
在高階瀏覽器裡,url會變成: http://localhost:801/$location/base/some1?foo=bar
在垃圾瀏覽器裡,url會變成: http://localhost:801/$location/base/#!/some1?foo=bar
注意事項:
1.href值以'/'開頭,會跳轉重新整理頁面.
2.a連結帶有target屬性,會跳轉重新整理頁面.
3.外鏈的話,直接寫絕對地址即可跳轉重新整理頁面
$location的雙向資料繫結
下面這段程式碼演示瞭如何實現讓$location.url和位址列的url雙向繫結:
<input type="text" ng-model="location" ng-model-options="{getterSetter:true}"/>
$scope.location=function(newLocation){
return $location.url(newLocation);
};
綜合例項
最後,我用所有關於$location的知識寫一個demo:
這個例項在angular學習筆記(三十一)-$location(1)的基礎上新增了本篇講到的知識:
1.新增base href標籤
2.hashbang和html5 history api處理瀏覽器相容
3.$location雙向資料繫結
4.a連結改變url
html:
<!DOCTYPE html> <html ng-app="locationApp"> <head> <title>21.1 $location</title> <meta charset="utf-8"> <base href="/$location/base/"> <script src="../angular-1.3.2.js"></script> <script src="../script.js"></script> </head>
<body ng-controller="locationCtrl">
<input type="text" ng-model="location" ng-model-options="{getterSetter:true}"/> <p>完整url路徑: <span>{{absurl}}</span></p> <p>url路徑(當前url#後面的內容,包括引數和雜湊值): <span>{{url}}</span> <button ng-click="changeUrl()">改變</button> </p> <p>相對路徑(也就是當前url#後面的內容,不包括引數): <span>{{path}}</span> <button ng-click="changePath()">改變</button> </p> <p>協議(比如http,https): <span>{{protocol}}</span></p> <p>主機名: <span>{{host}}</span></p> <p>埠號: <span>{{port}}</span></p> <p>雜湊值: <span>{{hash}}</span> <button ng-click="changeHash()">改變</button> </p> <p>search值序列化json: <span>{{search}}</span> <button ng-click="changeSearch_1()">改變1</button> <button ng-click="changeSearch_2()">改變2</button> <button ng-click="changeSearch_3()">改變3</button> <button ng-click="changeSearch_4()">改變4</button> <button ng-click="changeSearch_5()">改變5</button> <button ng-click="changeSearch_6()">改變6</button> </p> <a href="some1?foo=bar">/some1?foo=bar</a> | <a href="some2?foo=bar#myhash">/some2?foo=bar#myhash</a> | <a href="http://www.baidu.com">外部連結</a>
</body> </html>
js:
var locationApp = angular.module('locationApp',[]);
locationApp.config(function($locationProvider){
$locationProvider.html5Mode(true).hashPrefix('!');
});
locationApp.controller('locationCtrl',function($scope,$location,$timeout,$rootScope){
$scope.location=function(newLocation){
return $location.url(newLocation);
};
$scope.absurl = $location.absUrl();
$scope.url = $location.url();
$scope.path = $location.path();
$scope.protocol = $location.protocol();
$scope.host = $location.host();
$scope.port = $location.port();
$scope.hash = $location.hash();
$scope.search = $location.search();
$scope.refresh = function(){
$scope.absurl = $location.absUrl();
$scope.url = $location.url();
$scope.path = $location.path();
$scope.hash = $location.hash();
$scope.search = $location.search();
};
//重寫url部分,相應的absurl,url,path,hash,search都會改變
$scope.changeUrl = function(){
$location.url('/foo2?name=bunny2&age=12#myhash2');
};
//重寫path部分,相應的absurl,url,path都會改變
$scope.changePath = function(){
$location.path('/foo2/foo3');
};
//重寫hash部分,相應的absurl,url,hash都會改變
$scope.changeHash = function(){
$location.hash('myhash3');
};
//修改search部分(方法1),相應的absurl,url,search,hash都會改變
//指定兩個引數,第一個引數是屬性名,第二個引數是屬性值.
//如果屬性名是已有屬性,則修改,如果屬性名不是已有的,則新增.
//屬性值也可以是一個陣列,參考方法6
$scope.changeSearch_1 = function(){
$location.search('name','code_bunny');
};
//修改search部分(方法2),相應的absurl,url,search,hash都會改變
//指定兩個引數,第二個引數是null:刪除第一個引數所指定的屬性名.不再有這個屬性
//若第一個引數不是已有的,則不發生任何改變
$scope.changeSearch_2 = function(){
$location.search('age',null);
};
//修改search部分(方法3),相應的absurl,url,search,hash都會改變
//指定一個引數,json物件,直接重寫整個search部分.不管是不是已有屬性,全部重寫.
//這裡屬性的值可以是一個陣列,參考方法5
$scope.changeSearch_3 = function(){
$location.search({name:'papamibunny',age:16,love:'zxg'});
};
//修改search部分(方法4),相應的absurl,url,search,hash都會改變
//指定一個引數,字串,整個search部分就變為這個字串.注意是沒有屬性值的.
$scope.changeSearch_4 = function(){
$location.search('bunnybaobao');
};
//修改search部分(方法5),相應的absurl,url,search,hash都會改變
//其餘和方法3一樣.全部重寫search:
//指定一個引數,json格式,屬性值是一個陣列,那麼最後的search會變成name=code_bunny&name=white_bunny&name=hua_bunny
$scope.changeSearch_5 = function(){
$location.search({name:['code_bunny','white_bunny','hua_bunny']});
};
//修改search部分(方法6),相應的absurl,url,search,hash都會改變
//其餘和方法1一樣,修改指定的屬性名(或新增)
//第二個引數是一個陣列,最後search中的love部分會變成love=zxg&love=mitu
//它和方法5的區別,就像方法1和方法3的區別,一個是修改或新增某個屬性值,一個是重置整個search
$scope.changeSearch_6 = function(){
$location.search('love',['zxg','mitu']).replace();
};
//使用$location.replace(),則這一次的修改路徑不會被記錄到歷史記錄中,點選後退,不會後退到改變前的路徑,而是後退到改變前的路徑的改變前的路徑
$rootScope.$on('$locationChangeStart',function(){
console.log('開始改變$location')
});
$rootScope.$on('$locationChangeSuccess',function(){
$scope.refresh();
console.log('結束改變$location')
});
//這裡就算繫結了$routeChangeStart和$routeChangeSuccess,也不會被觸發,因為這裡沒有$route相關的服務.
});
*以上demo執行在我本地的http://localhost:801/這個wamp服務下.
1.新增base href標籤
新增了base href標籤後,頁面的基礎url就變為: http://localhost:801/$location/base/
2.hashbang和html5 history api處理瀏覽器相容
設定啟用html5的history api,設定bang識別符號為'!'
完成了1,2以後,在瀏覽器裡開啟 http://localhost:801/$location/base
在高階瀏覽器裡:
在ie9模式:
這裡初始狀態的$location.url,一個有'/',一個沒有'/',不過這個不用介意.不影響實際的使用的.
需要重視的是,當連結發生過改變(比如我點選了連結1/some1?foo=bar),然後重新整理頁面,在ie9下,它依然可以載入到該頁面,但是在chrome下是不行的,所以,在使用了history api的瀏覽器裡,需要服務端配置.通常,你要把所有連結都轉給應用的入口點(比如index.html)。
3.$location雙向資料繫結
可以使用1.3提供的ng-model-options的getterSetter,給input繫結location方法.這樣,input輸入內容,就會呼叫$location.url(newLocation)設定url,當呼叫$location.url('...')的時候,input元素的value值也會被同步.需要注意,直接在位址列輸入url是沒用的,不過這無所謂,因為應用中也不會有人要跳轉的時候手動輸地址...
4.a連結改變url
點選前面兩個連結,url會改變,不重新整理頁面.
完整程式碼:https://github.com/OOP-Code-Bunny/angular/tree/master/%24location
參考文獻:http://www.ngnice.com/docs/guide/$location (中文版的是1.2的,不是最新的) https://docs.angularjs.org/guide/$location