Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit 92a2e18

Browse files
committed
feat($location): add $locatonChange[begin|completed] event
This allows location change cancelation
1 parent 8aa18f0 commit 92a2e18

File tree

5 files changed

+246
-62
lines changed

5 files changed

+246
-62
lines changed

src/bootstrap/bootstrap-prettify.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,12 @@ directive.ngEmbedApp = ['$templateCache', '$browser', '$rootScope', '$location',
190190
$provide.value('$anchorScroll', angular.noop);
191191
$provide.value('$browser', $browser);
192192
$provide.provider('$location', function() {
193-
this.$get = function() { return $location; };
193+
this.$get = ['$rootScope', function($rootScope) {
194+
docsRootScope.$on('$locationChangeSuccess', function(event, oldUrl, newUrl) {
195+
$rootScope.$broadcast('$locationChangeSuccess', oldUrl, newUrl);
196+
});
197+
return $location;
198+
}];
194199
this.html5Mode = angular.noop;
195200
});
196201
$provide.decorator('$defer', ['$rootScope', '$delegate', function($rootScope, $delegate) {

src/ng/location.js

+67-43
Original file line numberDiff line numberDiff line change
@@ -408,7 +408,10 @@ function locationGetterSetter(property, preprocess) {
408408
* @requires $rootElement
409409
*
410410
* @description
411-
* The $location service parses the URL in the browser address bar (based on the {@link https://developer.mozilla.org/en/window.location window.location}) and makes the URL available to your application. Changes to the URL in the address bar are reflected into $location service and changes to $location are reflected into the browser address bar.
411+
* The $location service parses the URL in the browser address bar (based on the
412+
* {@link https://developer.mozilla.org/en/window.location window.location}) and makes the URL
413+
* available to your application. Changes to the URL in the address bar are reflected into
414+
* $location service and changes to $location are reflected into the browser address bar.
412415
*
413416
* **The $location service:**
414417
*
@@ -421,7 +424,8 @@ function locationGetterSetter(property, preprocess) {
421424
* - Clicks on a link.
422425
* - Represents the URL object as a set of methods (protocol, host, port, path, search, hash).
423426
*
424-
* For more information see {@link guide/dev_guide.services.$location Developer Guide: Angular Services: Using $location}
427+
* For more information see {@link guide/dev_guide.services.$location Developer Guide: Angular
428+
* Services: Using $location}
425429
*/
426430

427431
/**
@@ -470,65 +474,73 @@ function $LocationProvider(){
470474

471475
this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement',
472476
function( $rootScope, $browser, $sniffer, $rootElement) {
473-
var currentUrl,
477+
var $location,
474478
basePath = $browser.baseHref() || '/',
475479
pathPrefix = pathPrefixFromBase(basePath),
476-
initUrl = $browser.url();
480+
initUrl = $browser.url(),
481+
absUrlPrefix;
477482

478483
if (html5Mode) {
479484
if ($sniffer.history) {
480-
currentUrl = new LocationUrl(convertToHtml5Url(initUrl, basePath, hashPrefix), pathPrefix);
485+
$location = new LocationUrl(
486+
convertToHtml5Url(initUrl, basePath, hashPrefix),
487+
pathPrefix);
481488
} else {
482-
currentUrl = new LocationHashbangUrl(convertToHashbangUrl(initUrl, basePath, hashPrefix),
483-
hashPrefix);
489+
$location = new LocationHashbangUrl(
490+
convertToHashbangUrl(initUrl, basePath, hashPrefix),
491+
hashPrefix);
484492
}
493+
} else {
494+
$location = new LocationHashbangUrl(initUrl, hashPrefix);
495+
}
485496

486-
// link rewriting
487-
var u = currentUrl,
488-
absUrlPrefix = composeProtocolHostPort(u.protocol(), u.host(), u.port()) + pathPrefix;
497+
// link rewriting
498+
absUrlPrefix = composeProtocolHostPort(
499+
$location.protocol(), $location.host(), $location.port()) + pathPrefix;
489500

490-
$rootElement.bind('click', function(event) {
491-
// TODO(vojta): rewrite link when opening in new tab/window (in legacy browser)
492-
// currently we open nice url link and redirect then
501+
$rootElement.bind('click', function(event) {
502+
// TODO(vojta): rewrite link when opening in new tab/window (in legacy browser)
503+
// currently we open nice url link and redirect then
493504

494-
if (event.ctrlKey || event.metaKey || event.which == 2) return;
505+
if (event.ctrlKey || event.metaKey || event.which == 2) return;
495506

496-
var elm = jqLite(event.target);
507+
var elm = jqLite(event.target);
497508

498-
// traverse the DOM up to find first A tag
499-
while (elm.length && lowercase(elm[0].nodeName) !== 'a') {
500-
elm = elm.parent();
501-
}
509+
// traverse the DOM up to find first A tag
510+
while (elm.length && lowercase(elm[0].nodeName) !== 'a') {
511+
elm = elm.parent();
512+
}
502513

503-
var absHref = elm.prop('href');
514+
var absHref = elm.prop('href');
504515

505-
if (!absHref ||
506-
elm.attr('target') ||
507-
absHref.indexOf(absUrlPrefix) !== 0) { // link to different domain or base path
508-
return;
509-
}
516+
if (!absHref ||
517+
elm.attr('target') ||
518+
absHref.indexOf(absUrlPrefix) !== 0) { // link to different domain or base path
519+
return;
520+
}
521+
522+
// update location with href without the prefix
523+
$location.url(absHref.substr(absUrlPrefix.length));
524+
$rootScope.$apply();
525+
event.preventDefault();
526+
// hack to work around FF6 bug 684208 when scenario runner clicks on links
527+
window.angular['ff-684208-preventDefault'] = true;
528+
});
510529

511-
// update location with href without the prefix
512-
currentUrl.url(absHref.substr(absUrlPrefix.length));
513-
$rootScope.$apply();
514-
event.preventDefault();
515-
// hack to work around FF6 bug 684208 when scenario runner clicks on links
516-
window.angular['ff-684208-preventDefault'] = true;
517-
});
518-
} else {
519-
currentUrl = new LocationHashbangUrl(initUrl, hashPrefix);
520-
}
521530

522531
// rewrite hashbang url <> html5 url
523-
if (currentUrl.absUrl() != initUrl) {
524-
$browser.url(currentUrl.absUrl(), true);
532+
if ($location.absUrl() != initUrl) {
533+
$browser.url($location.absUrl(), true);
525534
}
526535

527536
// update $location when $browser url changes
528537
$browser.onUrlChange(function(newUrl) {
529-
if (currentUrl.absUrl() != newUrl) {
538+
if ($location.absUrl() != newUrl) {
530539
$rootScope.$evalAsync(function() {
531-
currentUrl.$$parse(newUrl);
540+
var oldUrl = $location.absUrl();
541+
542+
$location.$$parse(newUrl);
543+
afterLocationChange(oldUrl);
532544
});
533545
if (!$rootScope.$$phase) $rootScope.$digest();
534546
}
@@ -537,17 +549,29 @@ function $LocationProvider(){
537549
// update browser
538550
var changeCounter = 0;
539551
$rootScope.$watch(function $locationWatch() {
540-
if ($browser.url() != currentUrl.absUrl()) {
552+
var oldUrl = $browser.url();
553+
554+
if (!changeCounter || oldUrl != $location.absUrl()) {
541555
changeCounter++;
542556
$rootScope.$evalAsync(function() {
543-
$browser.url(currentUrl.absUrl(), currentUrl.$$replace);
544-
currentUrl.$$replace = false;
557+
if ($rootScope.$broadcast('$locationChangeStart', $location.absUrl(), oldUrl).
558+
defaultPrevented) {
559+
$location.$$parse(oldUrl);
560+
} else {
561+
$browser.url($location.absUrl(), $location.$$replace);
562+
$location.$$replace = false;
563+
afterLocationChange(oldUrl);
564+
}
545565
});
546566
}
547567

548568
return changeCounter;
549569
});
550570

551-
return currentUrl;
571+
return $location;
572+
573+
function afterLocationChange(oldUrl) {
574+
$rootScope.$broadcast('$locationChangeSuccess', $location.absUrl(), oldUrl);
575+
}
552576
}];
553577
}

src/ng/route.js

+2-3
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,6 @@ function $RouteProvider(){
286286
*/
287287

288288
var matcher = switchRouteMatcher,
289-
dirty = 0,
290289
forceReload = false,
291290
$route = {
292291
routes: routes,
@@ -304,12 +303,12 @@ function $RouteProvider(){
304303
* creates new scope, reinstantiates the controller.
305304
*/
306305
reload: function() {
307-
dirty++;
308306
forceReload = true;
307+
$rootScope.$evalAsync(updateRoute);
309308
}
310309
};
311310

312-
$rootScope.$watch(function() { return dirty + $location.url(); }, updateRoute);
311+
$rootScope.$on('$locationChangeSuccess', updateRoute);
313312

314313
return $route;
315314

test/ng/locationSpec.js

+144-13
Original file line numberDiff line numberDiff line change
@@ -791,19 +791,6 @@ describe('$location', function() {
791791
});
792792

793793

794-
it('should not rewrite when history disabled', function() {
795-
configureService('#new', false);
796-
inject(
797-
initBrowser(),
798-
initLocation(),
799-
function($browser) {
800-
browserTrigger(link, 'click');
801-
expectNoRewrite($browser);
802-
}
803-
);
804-
});
805-
806-
807794
it('should not rewrite full url links do different domain', function() {
808795
configureService('http://www.dot.abc/a?b=c', true);
809796
inject(
@@ -982,4 +969,148 @@ describe('$location', function() {
982969
});
983970
}
984971
});
972+
973+
974+
describe('location cancellation', function() {
975+
it('should fire $before/afterLocationChange event', inject(function($location, $browser, $rootScope, $log) {
976+
expect($browser.url()).toEqual('http://server/');
977+
978+
$rootScope.$on('$locationChangeStart', function(event, newUrl, oldUrl) {
979+
$log.info('before', newUrl, oldUrl, $browser.url());
980+
});
981+
$rootScope.$on('$locationChangeSuccess', function(event, newUrl, oldUrl) {
982+
$log.info('after', newUrl, oldUrl, $browser.url());
983+
});
984+
985+
expect($location.url()).toEqual('');
986+
$location.url('/somePath');
987+
expect($location.url()).toEqual('/somePath');
988+
expect($browser.url()).toEqual('http://server/');
989+
expect($log.info.logs).toEqual([]);
990+
991+
$rootScope.$apply();
992+
993+
expect($log.info.logs.shift()).
994+
toEqual(['before', 'http://server/#/somePath', 'http://server/', 'http://server/']);
995+
expect($log.info.logs.shift()).
996+
toEqual(['after', 'http://server/#/somePath', 'http://server/', 'http://server/#/somePath']);
997+
expect($location.url()).toEqual('/somePath');
998+
expect($browser.url()).toEqual('http://server/#/somePath');
999+
}));
1000+
1001+
1002+
it('should allow $locationChangeStart event cancellation', inject(function($location, $browser, $rootScope, $log) {
1003+
expect($browser.url()).toEqual('http://server/');
1004+
expect($location.url()).toEqual('');
1005+
1006+
$rootScope.$on('$locationChangeStart', function(event, newUrl, oldUrl) {
1007+
$log.info('before', newUrl, oldUrl, $browser.url());
1008+
event.preventDefault();
1009+
});
1010+
$rootScope.$on('$locationChangeCompleted', function(event, newUrl, oldUrl) {
1011+
throw Error('location should have been canceled');
1012+
});
1013+
1014+
expect($location.url()).toEqual('');
1015+
$location.url('/somePath');
1016+
expect($location.url()).toEqual('/somePath');
1017+
expect($browser.url()).toEqual('http://server/');
1018+
expect($log.info.logs).toEqual([]);
1019+
1020+
$rootScope.$apply();
1021+
1022+
expect($log.info.logs.shift()).
1023+
toEqual(['before', 'http://server/#/somePath', 'http://server/', 'http://server/']);
1024+
expect($log.info.logs[1]).toBeUndefined();
1025+
expect($location.url()).toEqual('');
1026+
expect($browser.url()).toEqual('http://server/');
1027+
}));
1028+
1029+
it ('should fire $locationChangeCompleted event when change from browser location bar',
1030+
inject(function($log, $location, $browser, $rootScope) {
1031+
$rootScope.$apply(); // clear initial $locationChangeStart
1032+
1033+
expect($browser.url()).toEqual('http://server/');
1034+
expect($location.url()).toEqual('');
1035+
1036+
$rootScope.$on('$locationChangeStart', function(event, newUrl, oldUrl) {
1037+
throw Error('there is no before when user enters URL directly to browser');
1038+
});
1039+
$rootScope.$on('$locationChangeSuccess', function(event, newUrl, oldUrl) {
1040+
$log.info('after', newUrl, oldUrl);
1041+
});
1042+
1043+
1044+
$browser.url('http://server/#/somePath');
1045+
$browser.poll();
1046+
1047+
expect($log.info.logs.shift()).
1048+
toEqual(['after', 'http://server/#/somePath', 'http://server/']);
1049+
})
1050+
);
1051+
1052+
1053+
it('should listen on click events on href and prevent browser default in hasbang mode', function() {
1054+
module(function() {
1055+
return function($rootElement, $compile, $rootScope) {
1056+
$rootElement.html('<a href="http://server/#/somePath">link</a>');
1057+
$compile($rootElement)($rootScope);
1058+
jqLite(document.body).append($rootElement);
1059+
}
1060+
});
1061+
1062+
inject(function($location, $rootScope, $browser, $rootElement) {
1063+
var log = '',
1064+
link = $rootElement.find('a');
1065+
1066+
1067+
$rootScope.$on('$locationChangeStart', function(event) {
1068+
event.preventDefault();
1069+
log += '$locationChangeStart';
1070+
});
1071+
$rootScope.$on('$locationChangeCompleted', function() {
1072+
throw new Error('after cancellation in hashbang mode');
1073+
});
1074+
1075+
browserTrigger(link, 'click');
1076+
1077+
expect(log).toEqual('$locationChangeStart');
1078+
expect($browser.url()).toEqual('http://server/');
1079+
1080+
dealoc($rootElement);
1081+
});
1082+
});
1083+
1084+
1085+
it('should listen on click events on href and prevent browser default in html5 mode', function() {
1086+
module(function($locationProvider) {
1087+
$locationProvider.html5Mode(true);
1088+
return function($rootElement, $compile, $rootScope) {
1089+
$rootElement.html('<a href="http://server/somePath">link</a>');
1090+
$compile($rootElement)($rootScope);
1091+
jqLite(document.body).append($rootElement);
1092+
}
1093+
});
1094+
1095+
inject(function($location, $rootScope, $browser, $rootElement) {
1096+
var log = '',
1097+
link = $rootElement.find('a');
1098+
1099+
$rootScope.$on('$locationChangeStart', function(event) {
1100+
event.preventDefault();
1101+
log += '$locationChangeStart';
1102+
});
1103+
$rootScope.$on('$locationChangeCompleted', function() {
1104+
throw new Error('after cancalation in html5 mode');
1105+
});
1106+
1107+
browserTrigger(link, 'click');
1108+
1109+
expect(log).toEqual('$locationChangeStart');
1110+
expect($browser.url()).toEqual('http://server/');
1111+
1112+
dealoc($rootElement);
1113+
});
1114+
});
1115+
});
9851116
});

0 commit comments

Comments
 (0)