mirror of
https://github.com/Hopiu/angular.js.git
synced 2026-03-18 07:50:22 +00:00
significant rewrite of the $location service
- don't update browser before and after eval instead - sync location properties before eval - sync location properties and update browser after eval - added tests - symplified the code - removed $location.toString() because it was not idempotent and useless This resolves the issue with issuing two $route.onHashChange calls when the $location was updated with a hashPath that needs to be encoded
This commit is contained in:
parent
b0be87f663
commit
23875cb330
5 changed files with 151 additions and 109 deletions
|
|
@ -52,6 +52,8 @@
|
||||||
|
|
||||||
- `angular.foreach` was renamed to `angular.forEach` to make the api consistent.
|
- `angular.foreach` was renamed to `angular.forEach` to make the api consistent.
|
||||||
|
|
||||||
|
- The `toString` method of the `angular.service.$location` service was removed.
|
||||||
|
|
||||||
|
|
||||||
# <angular/> 0.9.8 astral-projection (2010-12-23) #
|
# <angular/> 0.9.8 astral-projection (2010-12-23) #
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -232,6 +232,7 @@ function Browser(window, document, body, XHR, $log) {
|
||||||
* {@link angular.service.$location $location service} to monitor hash changes in angular apps.
|
* {@link angular.service.$location $location service} to monitor hash changes in angular apps.
|
||||||
*
|
*
|
||||||
* @param {function(event)} listener Listener function to be called when url hash changes.
|
* @param {function(event)} listener Listener function to be called when url hash changes.
|
||||||
|
* @return {function()} Returns the registered listener fn - handy if the fn is anonymous.
|
||||||
*/
|
*/
|
||||||
self.onHashChange = function(listener) {
|
self.onHashChange = function(listener) {
|
||||||
if ('onhashchange' in window) {
|
if ('onhashchange' in window) {
|
||||||
|
|
@ -245,6 +246,7 @@ function Browser(window, document, body, XHR, $log) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
return listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////
|
||||||
|
|
|
||||||
|
|
@ -70,23 +70,18 @@ angularServiceInject("$document", function(window){
|
||||||
*/
|
*/
|
||||||
angularServiceInject("$location", function($browser) {
|
angularServiceInject("$location", function($browser) {
|
||||||
var scope = this,
|
var scope = this,
|
||||||
location = {toString:toString, update:update, updateHash: updateHash},
|
location = {update:update, updateHash: updateHash},
|
||||||
lastBrowserUrl = $browser.getUrl(),
|
lastLocation = {};
|
||||||
lastLocationHref,
|
|
||||||
lastLocationHash;
|
|
||||||
|
|
||||||
$browser.onHashChange(function() {
|
$browser.onHashChange(function() { //register
|
||||||
update(lastBrowserUrl = $browser.getUrl());
|
update($browser.getUrl());
|
||||||
updateLastLocation();
|
copy(location, lastLocation);
|
||||||
scope.$eval();
|
scope.$eval();
|
||||||
});
|
})(); //initialize
|
||||||
|
|
||||||
this.$onEval(PRIORITY_FIRST, updateBrowser);
|
this.$onEval(PRIORITY_FIRST, sync);
|
||||||
this.$onEval(PRIORITY_LAST, updateBrowser);
|
this.$onEval(PRIORITY_LAST, updateBrowser);
|
||||||
|
|
||||||
update(lastBrowserUrl);
|
|
||||||
updateLastLocation();
|
|
||||||
|
|
||||||
return location;
|
return location;
|
||||||
|
|
||||||
// PUBLIC METHODS
|
// PUBLIC METHODS
|
||||||
|
|
@ -107,7 +102,7 @@ angularServiceInject("$location", function($browser) {
|
||||||
* scope.$location.update({host: 'www.google.com', protocol: 'https'});
|
* scope.$location.update({host: 'www.google.com', protocol: 'https'});
|
||||||
* scope.$location.update({hashPath: '/path', hashSearch: {a: 'b', x: true}});
|
* scope.$location.update({hashPath: '/path', hashSearch: {a: 'b', x: true}});
|
||||||
*
|
*
|
||||||
* @param {(string|Object)} href Full href as a string or hash object with properties
|
* @param {(string|Object)} href Full href as a string or object with properties
|
||||||
*/
|
*/
|
||||||
function update(href) {
|
function update(href) {
|
||||||
if (isString(href)) {
|
if (isString(href)) {
|
||||||
|
|
@ -163,62 +158,55 @@ angularServiceInject("$location", function($browser) {
|
||||||
update(hash);
|
update(hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @workInProgress
|
|
||||||
* @ngdoc method
|
|
||||||
* @name angular.service.$location#toString
|
|
||||||
* @methodOf angular.service.$location
|
|
||||||
*
|
|
||||||
* @description
|
|
||||||
* Returns string representation - href
|
|
||||||
*/
|
|
||||||
function toString() {
|
|
||||||
updateLocation();
|
|
||||||
return location.href;
|
|
||||||
}
|
|
||||||
|
|
||||||
// INNER METHODS
|
// INNER METHODS
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update location object
|
* Synchronizes all location object properties.
|
||||||
*
|
*
|
||||||
* User is allowed to change properties, so after property change,
|
* User is allowed to change properties, so after property change,
|
||||||
* location object is not in consistent state.
|
* location object is not in consistent state.
|
||||||
*
|
*
|
||||||
|
* Properties are synced with the following precedence order:
|
||||||
|
*
|
||||||
|
* - `$location.href`
|
||||||
|
* - `$location.hash`
|
||||||
|
* - everything else
|
||||||
|
*
|
||||||
* @example
|
* @example
|
||||||
* scope.$location.href = 'http://www.angularjs.org/path#a/b'
|
* scope.$location.href = 'http://www.angularjs.org/path#a/b'
|
||||||
* immediately after this call, other properties are still the old ones...
|
* immediately after this call, other properties are still the old ones...
|
||||||
*
|
*
|
||||||
* This method checks the changes and update location to the consistent state
|
* This method checks the changes and update location to the consistent state
|
||||||
*/
|
*/
|
||||||
function updateLocation() {
|
function sync() {
|
||||||
if (location.href == lastLocationHref) {
|
if (!equals(location, lastLocation)) {
|
||||||
if (location.hash == lastLocationHash) {
|
if (location.href != lastLocation.href) {
|
||||||
location.hash = composeHash(location);
|
update(location.href);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
location.href = composeHref(location);
|
if (location.hash != lastLocation.hash) {
|
||||||
|
var hash = parseHash(location.hash);
|
||||||
|
updateHash(hash.path, hash.search);
|
||||||
|
} else {
|
||||||
|
location.hash = composeHash(location);
|
||||||
|
location.href = composeHref(location);
|
||||||
|
}
|
||||||
|
update(location.href);
|
||||||
}
|
}
|
||||||
update(location.href);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Update information about last location
|
|
||||||
*/
|
|
||||||
function updateLastLocation() {
|
|
||||||
lastLocationHref = location.href;
|
|
||||||
lastLocationHash = location.hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If location has changed, update the browser
|
* If location has changed, update the browser
|
||||||
* This method is called at the end of $eval() phase
|
* This method is called at the end of $eval() phase
|
||||||
*/
|
*/
|
||||||
function updateBrowser() {
|
function updateBrowser() {
|
||||||
updateLocation();
|
sync();
|
||||||
|
|
||||||
if (location.href != lastLocationHref) {
|
if ($browser.getUrl() != location.href) {
|
||||||
$browser.setUrl(lastBrowserUrl = location.href);
|
$browser.setUrl(location.href);
|
||||||
updateLastLocation();
|
copy(location, lastLocation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
2
test/angular-mocks.js
vendored
2
test/angular-mocks.js
vendored
|
|
@ -77,6 +77,8 @@ function MockBrowser() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return listener;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -114,7 +114,8 @@ describe("service", function(){
|
||||||
$location = scope.$service('$location');
|
$location = scope.$service('$location');
|
||||||
});
|
});
|
||||||
|
|
||||||
it("update should update location object immediately", function() {
|
|
||||||
|
it("should update location object immediately when update is called", function() {
|
||||||
var href = 'http://host:123/p/a/t/h.html?query=value#path?key=value&flag&key2=';
|
var href = 'http://host:123/p/a/t/h.html?query=value#path?key=value&flag&key2=';
|
||||||
$location.update(href);
|
$location.update(href);
|
||||||
expect($location.href).toEqual(href);
|
expect($location.href).toEqual(href);
|
||||||
|
|
@ -140,43 +141,28 @@ describe("service", function(){
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('toString() should return actual representation', function() {
|
|
||||||
var href = 'http://host:123/p/a/t/h.html?query=value#path?key=value&flag&key2=';
|
|
||||||
$location.update(href);
|
|
||||||
expect($location.toString()).toEqual(href);
|
|
||||||
scope.$eval();
|
|
||||||
|
|
||||||
$location.host = 'new';
|
|
||||||
$location.path = '';
|
|
||||||
expect($location.toString()).toEqual('http://new:123?query=value#path?key=value&flag&key2=');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('toString() should not update browser', function() {
|
|
||||||
var url = $browser.getUrl();
|
|
||||||
$location.update('http://www.angularjs.org');
|
|
||||||
expect($location.toString()).toEqual('http://www.angularjs.org');
|
|
||||||
expect($browser.getUrl()).toEqual(url);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should update browser at the end of $eval', function() {
|
it('should update browser at the end of $eval', function() {
|
||||||
var url = $browser.getUrl();
|
var origBrowserUrl = $browser.getUrl();
|
||||||
$location.update('http://www.angularjs.org/');
|
$location.update('http://www.angularjs.org/');
|
||||||
$location.update({path: '/a/b'});
|
$location.update({path: '/a/b'});
|
||||||
expect($location.toString()).toEqual('http://www.angularjs.org/a/b');
|
expect($location.href).toEqual('http://www.angularjs.org/a/b');
|
||||||
expect($browser.getUrl()).toEqual(url);
|
expect($browser.getUrl()).toEqual(origBrowserUrl);
|
||||||
scope.$eval();
|
scope.$eval();
|
||||||
expect($browser.getUrl()).toEqual('http://www.angularjs.org/a/b');
|
expect($browser.getUrl()).toEqual('http://www.angularjs.org/a/b');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should update hashPath and hashSearch on hash update', function(){
|
it('should update hashPath and hashSearch on hash update', function(){
|
||||||
$location.update('http://server/#path?a=b');
|
$location.update('http://server/#path?a=b');
|
||||||
scope.$eval();
|
expect($location.hashPath).toEqual('path');
|
||||||
$location.update({hash: ''});
|
expect($location.hashSearch).toEqual({a:'b'});
|
||||||
|
|
||||||
|
$location.update({hash: ''});
|
||||||
expect($location.hashPath).toEqual('');
|
expect($location.hashPath).toEqual('');
|
||||||
expect($location.hashSearch).toEqual({});
|
expect($location.hashSearch).toEqual({});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should update hash on hashPath or hashSearch update', function() {
|
it('should update hash on hashPath or hashSearch update', function() {
|
||||||
$location.update('http://server/#path?a=b');
|
$location.update('http://server/#path?a=b');
|
||||||
scope.$eval();
|
scope.$eval();
|
||||||
|
|
@ -185,29 +171,37 @@ describe("service", function(){
|
||||||
expect($location.hash).toEqual('');
|
expect($location.hash).toEqual('');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should update hashPath and hashSearch on hash property change', function(){
|
|
||||||
|
it('should update hashPath and hashSearch on $location.hash change upon eval', function(){
|
||||||
$location.update('http://server/#path?a=b');
|
$location.update('http://server/#path?a=b');
|
||||||
scope.$eval();
|
scope.$eval();
|
||||||
$location.hash = '';
|
|
||||||
|
|
||||||
expect($location.toString()).toEqual('http://server/');
|
$location.hash = '';
|
||||||
|
scope.$eval();
|
||||||
|
|
||||||
|
expect($location.href).toEqual('http://server/');
|
||||||
expect($location.hashPath).toEqual('');
|
expect($location.hashPath).toEqual('');
|
||||||
expect($location.hashSearch).toEqual({});
|
expect($location.hashSearch).toEqual({});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should update hash on hashPath or hashSearch property change', function() {
|
|
||||||
|
it('should update hash on $location.hashPath or $location.hashSearch change upon eval',
|
||||||
|
function() {
|
||||||
$location.update('http://server/#path?a=b');
|
$location.update('http://server/#path?a=b');
|
||||||
scope.$eval();
|
scope.$eval();
|
||||||
$location.hashPath = '';
|
$location.hashPath = '';
|
||||||
$location.hashSearch = {};
|
$location.hashSearch = {};
|
||||||
|
|
||||||
expect($location.toString()).toEqual('http://server/');
|
scope.$eval();
|
||||||
|
|
||||||
|
expect($location.href).toEqual('http://server/');
|
||||||
expect($location.hash).toEqual('');
|
expect($location.hash).toEqual('');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should update hash before any processing', function(){
|
|
||||||
scope = compile('<div>');
|
it('should sync $location upon eval before watches are fired', function(){
|
||||||
scope.$location = scope.$service('$location');
|
scope.$location = scope.$service('$location'); //publish to the scope for $watch
|
||||||
|
|
||||||
var log = '';
|
var log = '';
|
||||||
scope.$watch('$location.hash', function(){
|
scope.$watch('$location.hash', function(){
|
||||||
log += this.$location.hashPath + ';';
|
log += this.$location.hashPath + ';';
|
||||||
|
|
@ -217,48 +211,102 @@ describe("service", function(){
|
||||||
log = '';
|
log = '';
|
||||||
scope.$location.hash = '/abc';
|
scope.$location.hash = '/abc';
|
||||||
scope.$eval();
|
scope.$eval();
|
||||||
|
expect(scope.$location.hash).toEqual('/abc');
|
||||||
expect(log).toEqual('/abc;');
|
expect(log).toEqual('/abc;');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('udpate() should accept hash object and update only given properties', function() {
|
|
||||||
$location.update("http://host:123/p/a/t/h.html?query=value#path?key=value&flag&key2=");
|
|
||||||
$location.update({host: 'new', port: 24});
|
|
||||||
|
|
||||||
expect($location.host).toEqual('new');
|
describe('sync', function() {
|
||||||
expect($location.port).toEqual(24);
|
it('should update hash with escaped hashPath', function() {
|
||||||
expect($location.protocol).toEqual('http');
|
$location.hashPath = 'foo=bar';
|
||||||
expect($location.href).toEqual("http://new:24/p/a/t/h.html?query=value#path?key=value&flag&key2=");
|
scope.$eval();
|
||||||
|
expect($location.hash).toBe('foo%3Dbar');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should give $location.href the highest precedence', function() {
|
||||||
|
$location.hashPath = 'hashPath';
|
||||||
|
$location.hashSearch = {hash:'search'};
|
||||||
|
$location.hash = 'hash';
|
||||||
|
$location.port = '333';
|
||||||
|
$location.host = 'host';
|
||||||
|
$location.href = 'https://hrefhost:23/hrefpath';
|
||||||
|
|
||||||
|
scope.$eval();
|
||||||
|
|
||||||
|
expect($location).toEqualData({href: 'https://hrefhost:23/hrefpath',
|
||||||
|
protocol: 'https',
|
||||||
|
host: 'hrefhost',
|
||||||
|
port: '23',
|
||||||
|
path: '/hrefpath',
|
||||||
|
search: {},
|
||||||
|
hash: '',
|
||||||
|
hashPath: '',
|
||||||
|
hashSearch: {}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should give $location.hash second highest precedence', function() {
|
||||||
|
$location.hashPath = 'hashPath';
|
||||||
|
$location.hashSearch = {hash:'search'};
|
||||||
|
$location.hash = 'hash';
|
||||||
|
$location.port = '333';
|
||||||
|
$location.host = 'host';
|
||||||
|
$location.path = '/path';
|
||||||
|
|
||||||
|
scope.$eval();
|
||||||
|
|
||||||
|
expect($location).toEqualData({href: 'http://host:333/path#hash',
|
||||||
|
protocol: 'http',
|
||||||
|
host: 'host',
|
||||||
|
port: '333',
|
||||||
|
path: '/path',
|
||||||
|
search: {},
|
||||||
|
hash: 'hash',
|
||||||
|
hashPath: 'hash',
|
||||||
|
hashSearch: {}
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('updateHash() should accept one string argument to update path', function() {
|
describe('update()', function() {
|
||||||
$location.updateHash('path');
|
it('should accept hash object and update only given properties', function() {
|
||||||
expect($location.hash).toEqual('path');
|
$location.update("http://host:123/p/a/t/h.html?query=value#path?key=value&flag&key2=");
|
||||||
expect($location.hashPath).toEqual('path');
|
$location.update({host: 'new', port: 24});
|
||||||
|
|
||||||
|
expect($location.host).toEqual('new');
|
||||||
|
expect($location.port).toEqual(24);
|
||||||
|
expect($location.protocol).toEqual('http');
|
||||||
|
expect($location.href).toEqual("http://new:24/p/a/t/h.html?query=value#path?key=value&flag&key2=");
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove # if hash is empty', function() {
|
||||||
|
$location.update('http://www.angularjs.org/index.php#');
|
||||||
|
expect($location.href).toEqual('http://www.angularjs.org/index.php');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('updateHash() should accept one hash argument to update search', function() {
|
|
||||||
$location.updateHash({a: 'b'});
|
|
||||||
expect($location.hash).toEqual('?a=b');
|
|
||||||
expect($location.hashSearch).toEqual({a: 'b'});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('updateHash() should accept path and search both', function() {
|
describe('updateHash()', function() {
|
||||||
$location.updateHash('path', {a: 'b'});
|
it('should accept single string argument to update path', function() {
|
||||||
expect($location.hash).toEqual('path?a=b');
|
$location.updateHash('path');
|
||||||
expect($location.hashSearch).toEqual({a: 'b'});
|
expect($location.hash).toEqual('path');
|
||||||
expect($location.hashPath).toEqual('path');
|
expect($location.hashPath).toEqual('path');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should remove # if hash is empty', function() {
|
it('should accept single object argument to update search', function() {
|
||||||
$location.update('http://www.angularjs.org/index.php#');
|
$location.updateHash({a: 'b'});
|
||||||
expect($location.href).toEqual('http://www.angularjs.org/index.php');
|
expect($location.hash).toEqual('?a=b');
|
||||||
});
|
expect($location.hashSearch).toEqual({a: 'b'});
|
||||||
|
});
|
||||||
it('should not change browser\'s url with empty hash', function() {
|
|
||||||
$browser.setUrl('http://www.angularjs.org/index.php#');
|
it('should accept path string and search object arguments to update both', function() {
|
||||||
spyOn($browser, 'setUrl');
|
$location.updateHash('path', {a: 'b'});
|
||||||
$browser.poll();
|
expect($location.hash).toEqual('path?a=b');
|
||||||
expect($browser.setUrl).not.toHaveBeenCalled();
|
expect($location.hashSearch).toEqual({a: 'b'});
|
||||||
|
expect($location.hashPath).toEqual('path');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue