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

feat($location): allow to change location during $locationChangeStart #9678

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 24 additions & 5 deletions src/ng/location.js
Original file line number Diff line number Diff line change
Expand Up @@ -829,11 +829,21 @@ function $LocationProvider() {
$rootScope.$evalAsync(function() {
var oldUrl = $location.absUrl();
var oldState = $location.$$state;
var defaultPrevented;

$location.$$parse(newUrl);
$location.$$state = newState;
if ($rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl,
newState, oldState).defaultPrevented) {

defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl,
newState, oldState).defaultPrevented;

// only process the location change if the location Url was not changed by a
// `$locationChangeStart` event handler.
if ($location.absUrl() !== newUrl) {
return;
}

if (defaultPrevented) {
$location.$$parse(oldUrl);
$location.$$state = oldState;
setBrowserUrlWithFallback(oldUrl, false, oldState);
Expand All @@ -857,13 +867,22 @@ function $LocationProvider() {
initializing = false;

$rootScope.$evalAsync(function() {
if ($rootScope.$broadcast('$locationChangeStart', $location.absUrl(), oldUrl,
$location.$$state, oldState).defaultPrevented) {
var newUrl = $location.absUrl();
var defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl,
$location.$$state, oldState).defaultPrevented;

// only process the location change if the location Url was not changed by a
// `$locationChangeStart` event handler.
if ($location.absUrl() !== newUrl) {
return;
}

if (defaultPrevented) {
$location.$$parse(oldUrl);
$location.$$state = oldState;
} else {
if (urlOrStateChanged) {
setBrowserUrlWithFallback($location.absUrl(), currentReplace,
setBrowserUrlWithFallback(newUrl, currentReplace,
oldState === $location.$$state ? null : $location.$$state);
}
afterLocationChange(oldUrl, oldState);
Expand Down
149 changes: 149 additions & 0 deletions test/ng/locationSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1690,6 +1690,95 @@ describe('$location', function() {
expect($browser.url()).toEqual('http://server/');
}));

it('should allow redirect during $locationChangeStart',
inject(function($location, $browser, $rootScope, $log) {
$rootScope.$on('$locationChangeStart', function(event, newUrl, oldUrl) {
$log.info('before', newUrl, oldUrl, $browser.url());
if (newUrl === 'http://server/#/somePath') {
$location.url('/redirectPath');
}
});
$rootScope.$on('$locationChangeSuccess', function(event, newUrl, oldUrl) {
$log.info('after', newUrl, oldUrl, $browser.url());
});

$location.url('/somePath');
$rootScope.$apply();

expect($log.info.logs.shift()).
toEqual(['before', 'http://server/#/somePath', 'http://server/', 'http://server/']);
expect($log.info.logs.shift()).
toEqual(['before', 'http://server/#/redirectPath', 'http://server/', 'http://server/']);
expect($log.info.logs.shift()).
toEqual(['after', 'http://server/#/redirectPath', 'http://server/',
'http://server/#/redirectPath']);

expect($location.url()).toEqual('/redirectPath');
expect($browser.url()).toEqual('http://server/#/redirectPath');
})
);

it('should allow redirect during $locationChangeStart even if default prevented',
inject(function($location, $browser, $rootScope, $log) {
$rootScope.$on('$locationChangeStart', function(event, newUrl, oldUrl) {
$log.info('before', newUrl, oldUrl, $browser.url());
if (newUrl === 'http://server/#/somePath') {
event.preventDefault();
$location.url('/redirectPath');
}
});
$rootScope.$on('$locationChangeSuccess', function(event, newUrl, oldUrl) {
$log.info('after', newUrl, oldUrl, $browser.url());
});

$location.url('/somePath');
$rootScope.$apply();

expect($log.info.logs.shift()).
toEqual(['before', 'http://server/#/somePath', 'http://server/', 'http://server/']);
expect($log.info.logs.shift()).
toEqual(['before', 'http://server/#/redirectPath', 'http://server/', 'http://server/']);
expect($log.info.logs.shift()).
toEqual(['after', 'http://server/#/redirectPath', 'http://server/',
'http://server/#/redirectPath']);

expect($location.url()).toEqual('/redirectPath');
expect($browser.url()).toEqual('http://server/#/redirectPath');
})
);

it('should allow multiple redirect during $locationChangeStart',
inject(function($location, $browser, $rootScope, $log) {
$rootScope.$on('$locationChangeStart', function(event, newUrl, oldUrl) {
$log.info('before', newUrl, oldUrl, $browser.url());
if (newUrl === 'http://server/#/somePath') {
$location.url('/redirectPath');
} else if (newUrl === 'http://server/#/redirectPath') {
$location.url('/redirectPath2');
}
});
$rootScope.$on('$locationChangeSuccess', function(event, newUrl, oldUrl) {
$log.info('after', newUrl, oldUrl, $browser.url());
});

$location.url('/somePath');
$rootScope.$apply();

expect($log.info.logs.shift()).
toEqual(['before', 'http://server/#/somePath', 'http://server/', 'http://server/']);
expect($log.info.logs.shift()).
toEqual(['before', 'http://server/#/redirectPath', 'http://server/', 'http://server/']);
expect($log.info.logs.shift()).
toEqual(['before', 'http://server/#/redirectPath2', 'http://server/', 'http://server/']);
expect($log.info.logs.shift()).
toEqual(['after', 'http://server/#/redirectPath2', 'http://server/',
'http://server/#/redirectPath2']);

expect($location.url()).toEqual('/redirectPath2');
expect($browser.url()).toEqual('http://server/#/redirectPath2');
})
);

it ('should fire $locationChangeSuccess event when change from browser location bar',
inject(function($log, $location, $browser, $rootScope) {
$rootScope.$apply(); // clear initial $locationChangeStart
Expand All @@ -1715,6 +1804,66 @@ describe('$location', function() {
})
);

it('should allow redirect during browser url change',
inject(function($location, $browser, $rootScope, $log) {
$rootScope.$on('$locationChangeStart', function(event, newUrl, oldUrl) {
$log.info('before', newUrl, oldUrl, $browser.url());
if (newUrl === 'http://server/#/somePath') {
$location.url('/redirectPath');
}
});
$rootScope.$on('$locationChangeSuccess', function(event, newUrl, oldUrl) {
$log.info('after', newUrl, oldUrl, $browser.url());
});

$browser.url('http://server/#/somePath');
$browser.poll();

expect($log.info.logs.shift()).
toEqual(['before', 'http://server/#/somePath', 'http://server/',
'http://server/#/somePath']);
expect($log.info.logs.shift()).
toEqual(['before', 'http://server/#/redirectPath', 'http://server/#/somePath',
'http://server/#/somePath']);
expect($log.info.logs.shift()).
toEqual(['after', 'http://server/#/redirectPath', 'http://server/#/somePath',
'http://server/#/redirectPath']);

expect($location.url()).toEqual('/redirectPath');
expect($browser.url()).toEqual('http://server/#/redirectPath');
})
);

it('should allow redirect during browser url change even if default prevented',
inject(function($location, $browser, $rootScope, $log) {
$rootScope.$on('$locationChangeStart', function(event, newUrl, oldUrl) {
$log.info('before', newUrl, oldUrl, $browser.url());
if (newUrl === 'http://server/#/somePath') {
event.preventDefault();
$location.url('/redirectPath');
}
});
$rootScope.$on('$locationChangeSuccess', function(event, newUrl, oldUrl) {
$log.info('after', newUrl, oldUrl, $browser.url());
});

$browser.url('http://server/#/somePath');
$browser.poll();

expect($log.info.logs.shift()).
toEqual(['before', 'http://server/#/somePath', 'http://server/',
'http://server/#/somePath']);
expect($log.info.logs.shift()).
toEqual(['before', 'http://server/#/redirectPath', 'http://server/#/somePath',
'http://server/#/somePath']);
expect($log.info.logs.shift()).
toEqual(['after', 'http://server/#/redirectPath', 'http://server/#/somePath',
'http://server/#/redirectPath']);

expect($location.url()).toEqual('/redirectPath');
expect($browser.url()).toEqual('http://server/#/redirectPath');
})
);

it('should listen on click events on href and prevent browser default in hashbang mode', function() {
module(function() {
Expand Down
30 changes: 30 additions & 0 deletions test/ngRoute/routeSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,36 @@ describe('$route', function() {
});
});

it('should allow redirects while handling $routeChangeStart', function() {
module(function($routeProvider) {
$routeProvider.when('/some', {
id: 'some', template: 'Some functionality'
});
$routeProvider.when('/redirect', {
id: 'redirect'
});
});
module(provideLog);
inject(function($route, $location, $rootScope, $compile, log) {
$rootScope.$on('$routeChangeStart', function(event, next, current) {
if (next.id === 'some') {
$location.path('/redirect');
}
});
$compile('<div><div ng-view></div></div>')($rootScope);
$rootScope.$on('$routeChangeStart', log.fn('routeChangeStart'));
$rootScope.$on('$routeChangeError', log.fn('routeChangeError'));
$rootScope.$on('$routeChangeSuccess', log.fn('routeChangeSuccess'));
$rootScope.$apply(function() {
$location.path('/some');
});

expect($route.current.id).toBe('redirect');
expect($location.path()).toBe('/redirect');
expect(log).toEqual(['routeChangeStart', 'routeChangeStart', 'routeChangeSuccess']);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i feel like it would look weird if we emit $routeChangeStart without a matching $routeChangeError or $routeChangeSuccess --- do you think this will cause issues for people?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doing the current workaround as suggested in the original issue will result in the exact same event sequence, so I don't see a problem with this...

The only difference is that instead of doing:

$rootScope.$on("$routeChangeStart", function (event, next, current) {
  if (next && next.secure) {
    event.preventDefault();
    $rootScope.$evalAsync(function() {
      $location.path('/c');
    });
  }
});

You would do this:

$rootScope.$on("$routeChangeStart", function (event, next, current) {
  if (next && next.secure) {
    $location.path('/c');
  }
});

});
});

it('should route and fire change event', function() {
var log = '',
lastRoute,
Expand Down