Skip to content

Commit c72d8ce

Browse files
committed
feat($urlRouter): defer URL change interception
$urlRouter's listener can now be made to take lower priority than custom listeners by calling `$urlRouterProvider.deferIntercept()` to detach the listener, and `$urlRouter.listen()` to reattach it.
1 parent 27d382c commit c72d8ce

File tree

2 files changed

+123
-29
lines changed

2 files changed

+123
-29
lines changed

Diff for: src/urlRouter.js

+70-9
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,7 @@
1616
*/
1717
$UrlRouterProvider.$inject = ['$locationProvider', '$urlMatcherFactoryProvider'];
1818
function $UrlRouterProvider( $locationProvider, $urlMatcherFactory) {
19-
var rules = [],
20-
otherwise = null;
19+
var rules = [], otherwise = null, interceptDeferred = false, listener;
2120

2221
// Returns a string that is a prefix of all strings matching the RegExp
2322
function regExpPrefix(re) {
@@ -38,7 +37,7 @@ function $UrlRouterProvider( $locationProvider, $urlMatcherFactory) {
3837
* @methodOf ui.router.router.$urlRouterProvider
3938
*
4039
* @description
41-
* Defines rules that are used by `$urlRouterProvider to find matches for
40+
* Defines rules that are used by `$urlRouterProvider` to find matches for
4241
* specific URLs.
4342
*
4443
* @example
@@ -61,7 +60,7 @@ function $UrlRouterProvider( $locationProvider, $urlMatcherFactory) {
6160
* @param {object} rule Handler function that takes `$injector` and `$location`
6261
* services as arguments. You can use them to return a valid path as a string.
6362
*
64-
* @return {object} $urlRouterProvider - $urlRouterProvider instance
63+
* @return {object} `$urlRouterProvider` - `$urlRouterProvider` instance
6564
*/
6665
this.rule = function (rule) {
6766
if (!isFunction(rule)) throw new Error("'rule' must be a function");
@@ -75,7 +74,7 @@ function $UrlRouterProvider( $locationProvider, $urlMatcherFactory) {
7574
* @methodOf ui.router.router.$urlRouterProvider
7675
*
7776
* @description
78-
* Defines a path that is used when an invalied route is requested.
77+
* Defines a path that is used when an invalid route is requested.
7978
*
8079
* @example
8180
* <pre>
@@ -98,7 +97,7 @@ function $UrlRouterProvider( $locationProvider, $urlMatcherFactory) {
9897
* rule that returns the url path. The function version is passed two params:
9998
* `$injector` and `$location` services.
10099
*
101-
* @return {object} $urlRouterProvider - $urlRouterProvider instance
100+
* @return {object} `$urlRouterProvider` - `$urlRouterProvider` instance
102101
*/
103102
this.otherwise = function (rule) {
104103
if (isString(rule)) {
@@ -124,8 +123,8 @@ function $UrlRouterProvider( $locationProvider, $urlMatcherFactory) {
124123
*
125124
* @description
126125
* Registers a handler for a given url matching. if handle is a string, it is
127-
* treated as a redirect, and is interpolated according to the syyntax of match
128-
* (i.e. like String.replace() for RegExp, or like a UrlMatcher pattern otherwise).
126+
* treated as a redirect, and is interpolated according to the syntax of match
127+
* (i.e. like `String.replace()` for `RegExp`, or like a `UrlMatcher` pattern otherwise).
129128
*
130129
* If the handler is a function, it is injectable. It gets invoked if `$location`
131130
* matches. You have the option of inject the match object as `$match`.
@@ -197,6 +196,59 @@ function $UrlRouterProvider( $locationProvider, $urlMatcherFactory) {
197196
throw new Error("invalid 'what' in when()");
198197
};
199198

199+
/**
200+
* @ngdoc function
201+
* @name ui.router.router.$urlRouterProvider#deferIntercept
202+
* @methodOf ui.router.router.$urlRouterProvider
203+
*
204+
* @description
205+
* Disables (or enables) deferring location change interception.
206+
*
207+
* If you wish to customize the behavior of syncing the URL (for example, if you wish to
208+
* defer a transition but maintain the current URL), call this method at configuration time.
209+
* Then, at run time, call `$urlRouter.listen()` after you have configured your own
210+
* `$locationChangeSuccess` event handler.
211+
*
212+
* @example
213+
* <pre>
214+
* var app = angular.module('app', ['ui.router.router']);
215+
*
216+
* app.config(function ($urlRouterProvider) {
217+
*
218+
* // Prevent $urlRouter from automatically intercepting URL changes;
219+
* // this allows you to configure custom behavior in between
220+
* // location changes and route synchronization:
221+
* $urlRouterProvider.deferIntercept();
222+
*
223+
* }).run(function ($rootScope, $urlRouter, UserService) {
224+
*
225+
* $rootScope.$on('$locationChangeSuccess', function(e) {
226+
* // UserService is an example service for managing user state
227+
* if (UserService.isLoggedIn()) return;
228+
*
229+
* // Prevent $urlRouter's default handler from firing
230+
* e.preventDefault();
231+
*
232+
* UserService.handleLogin().then(function() {
233+
* // Once the user has logged in, sync the current URL
234+
* // to the router:
235+
* $urlRouter.sync();
236+
* });
237+
* });
238+
*
239+
* // Configures $urlRouter's listener *after* your custom listener
240+
* $urlRouter.listen();
241+
* });
242+
* </pre>
243+
*
244+
* @param {boolean} defer Indicates whether to defer location change interception. Passing
245+
no parameter is equivalent to `true`.
246+
*/
247+
this.deferIntercept = function (defer) {
248+
if (defer === undefined) defer = true;
249+
interceptDeferred = defer;
250+
}
251+
200252
/**
201253
* @ngdoc object
202254
* @name ui.router.router.$urlRouter
@@ -242,7 +294,12 @@ function $UrlRouterProvider( $locationProvider, $urlMatcherFactory) {
242294
if (otherwise) check(otherwise);
243295
}
244296

245-
$rootScope.$on('$locationChangeSuccess', update);
297+
function listen() {
298+
listener = listener || $rootScope.$on('$locationChangeSuccess', update);
299+
return listener;
300+
}
301+
302+
if (!interceptDeferred) listen();
246303

247304
return {
248305
/**
@@ -275,6 +332,10 @@ function $UrlRouterProvider( $locationProvider, $urlMatcherFactory) {
275332
update();
276333
},
277334

335+
listen: function() {
336+
return listen();
337+
},
338+
278339
update: function(read) {
279340
if (read) {
280341
location = $location.url();

Diff for: test/urlRouterSpec.js

+53-20
Original file line numberDiff line numberDiff line change
@@ -2,40 +2,73 @@ describe("UrlRouter", function () {
22

33
var $urp, $ur, location, match, scope;
44

5-
beforeEach(function() {
6-
angular.module('ui.router.router.test', function() {}).config(function ($urlRouterProvider) {
7-
$urp = $urlRouterProvider;
5+
describe("provider", function () {
86

9-
$urp.rule(function ($injector, $location) {
10-
var path = $location.path();
11-
if (!/baz/.test(path)) return false;
12-
return path.replace('baz', 'b4z');
13-
}).when('/foo/:param', function($match) {
14-
match = ['/foo/:param', $match];
15-
}).when('/bar', function($match) {
16-
match = ['/bar', $match];
7+
beforeEach(function() {
8+
angular.module('ui.router.router.test', function() {}).config(function ($urlRouterProvider) {
9+
$urlRouterProvider.deferIntercept();
10+
$urp = $urlRouterProvider;
1711
});
18-
});
1912

20-
module('ui.router.router', 'ui.router.router.test');
13+
module('ui.router.router', 'ui.router.router.test');
2114

22-
inject(function($rootScope, $location, $injector) {
23-
scope = $rootScope.$new();
24-
location = $location;
25-
$ur = $injector.invoke($urp.$get);
15+
inject(function($rootScope, $location, $injector) {
16+
scope = $rootScope.$new();
17+
location = $location;
18+
$ur = $injector.invoke($urp.$get);
19+
});
2620
});
27-
});
28-
29-
describe("provider", function () {
3021

3122
it("should throw on non-function rules", function () {
3223
expect(function() { $urp.rule(null); }).toThrow("'rule' must be a function")
3324
expect(function() { $urp.otherwise(null); }).toThrow("'rule' must be a function")
3425
});
3526

27+
it("should allow location changes to be deferred", inject(function ($urlRouter, $location, $rootScope) {
28+
var log = [];
29+
30+
$urp.rule(function ($injector, $location) {
31+
log.push($location.path());
32+
});
33+
34+
$location.path("/foo");
35+
$rootScope.$broadcast("$locationChangeSuccess");
36+
37+
expect(log).toEqual([]);
38+
39+
$urlRouter.listen();
40+
$rootScope.$broadcast("$locationChangeSuccess");
41+
42+
expect(log).toEqual(["/foo"]);
43+
}));
3644
});
3745

3846
describe("service", function() {
47+
48+
beforeEach(function() {
49+
angular.module('ui.router.router.test', function() {}).config(function ($urlRouterProvider) {
50+
$urp = $urlRouterProvider;
51+
52+
$urp.rule(function ($injector, $location) {
53+
var path = $location.path();
54+
if (!/baz/.test(path)) return false;
55+
return path.replace('baz', 'b4z');
56+
}).when('/foo/:param', function($match) {
57+
match = ['/foo/:param', $match];
58+
}).when('/bar', function($match) {
59+
match = ['/bar', $match];
60+
});
61+
});
62+
63+
module('ui.router.router', 'ui.router.router.test');
64+
65+
inject(function($rootScope, $location, $injector) {
66+
scope = $rootScope.$new();
67+
location = $location;
68+
$ur = $injector.invoke($urp.$get);
69+
});
70+
});
71+
3972
it("should execute rewrite rules", function () {
4073
location.path("/foo");
4174
scope.$emit("$locationChangeSuccess");

0 commit comments

Comments
 (0)