Skip to content

Commit

Permalink
feat($urlRouter): defer URL change interception
Browse files Browse the repository at this point in the history
$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.
  • Loading branch information
nateabele committed Apr 16, 2014
1 parent 27d382c commit c72d8ce
Show file tree
Hide file tree
Showing 2 changed files with 123 additions and 29 deletions.
79 changes: 70 additions & 9 deletions src/urlRouter.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@
*/
$UrlRouterProvider.$inject = ['$locationProvider', '$urlMatcherFactoryProvider'];
function $UrlRouterProvider( $locationProvider, $urlMatcherFactory) {
var rules = [],
otherwise = null;
var rules = [], otherwise = null, interceptDeferred = false, listener;

// Returns a string that is a prefix of all strings matching the RegExp
function regExpPrefix(re) {
Expand All @@ -38,7 +37,7 @@ function $UrlRouterProvider( $locationProvider, $urlMatcherFactory) {
* @methodOf ui.router.router.$urlRouterProvider
*
* @description
* Defines rules that are used by `$urlRouterProvider to find matches for
* Defines rules that are used by `$urlRouterProvider` to find matches for
* specific URLs.
*
* @example
Expand All @@ -61,7 +60,7 @@ function $UrlRouterProvider( $locationProvider, $urlMatcherFactory) {
* @param {object} rule Handler function that takes `$injector` and `$location`
* services as arguments. You can use them to return a valid path as a string.
*
* @return {object} $urlRouterProvider - $urlRouterProvider instance
* @return {object} `$urlRouterProvider` - `$urlRouterProvider` instance
*/
this.rule = function (rule) {
if (!isFunction(rule)) throw new Error("'rule' must be a function");
Expand All @@ -75,7 +74,7 @@ function $UrlRouterProvider( $locationProvider, $urlMatcherFactory) {
* @methodOf ui.router.router.$urlRouterProvider
*
* @description
* Defines a path that is used when an invalied route is requested.
* Defines a path that is used when an invalid route is requested.
*
* @example
* <pre>
Expand All @@ -98,7 +97,7 @@ function $UrlRouterProvider( $locationProvider, $urlMatcherFactory) {
* rule that returns the url path. The function version is passed two params:
* `$injector` and `$location` services.
*
* @return {object} $urlRouterProvider - $urlRouterProvider instance
* @return {object} `$urlRouterProvider` - `$urlRouterProvider` instance
*/
this.otherwise = function (rule) {
if (isString(rule)) {
Expand All @@ -124,8 +123,8 @@ function $UrlRouterProvider( $locationProvider, $urlMatcherFactory) {
*
* @description
* Registers a handler for a given url matching. if handle is a string, it is
* treated as a redirect, and is interpolated according to the syyntax of match
* (i.e. like String.replace() for RegExp, or like a UrlMatcher pattern otherwise).
* treated as a redirect, and is interpolated according to the syntax of match
* (i.e. like `String.replace()` for `RegExp`, or like a `UrlMatcher` pattern otherwise).
*
* If the handler is a function, it is injectable. It gets invoked if `$location`
* matches. You have the option of inject the match object as `$match`.
Expand Down Expand Up @@ -197,6 +196,59 @@ function $UrlRouterProvider( $locationProvider, $urlMatcherFactory) {
throw new Error("invalid 'what' in when()");
};

/**
* @ngdoc function
* @name ui.router.router.$urlRouterProvider#deferIntercept
* @methodOf ui.router.router.$urlRouterProvider
*
* @description
* Disables (or enables) deferring location change interception.
*
* If you wish to customize the behavior of syncing the URL (for example, if you wish to
* defer a transition but maintain the current URL), call this method at configuration time.
* Then, at run time, call `$urlRouter.listen()` after you have configured your own
* `$locationChangeSuccess` event handler.
*
* @example
* <pre>
* var app = angular.module('app', ['ui.router.router']);
*
* app.config(function ($urlRouterProvider) {
*
* // Prevent $urlRouter from automatically intercepting URL changes;
* // this allows you to configure custom behavior in between
* // location changes and route synchronization:
* $urlRouterProvider.deferIntercept();
*
* }).run(function ($rootScope, $urlRouter, UserService) {
*
* $rootScope.$on('$locationChangeSuccess', function(e) {
* // UserService is an example service for managing user state
* if (UserService.isLoggedIn()) return;
*
* // Prevent $urlRouter's default handler from firing
* e.preventDefault();
*
* UserService.handleLogin().then(function() {
* // Once the user has logged in, sync the current URL
* // to the router:
* $urlRouter.sync();
* });
* });
*
* // Configures $urlRouter's listener *after* your custom listener
* $urlRouter.listen();
* });
* </pre>
*
* @param {boolean} defer Indicates whether to defer location change interception. Passing
no parameter is equivalent to `true`.
*/
this.deferIntercept = function (defer) {
if (defer === undefined) defer = true;
interceptDeferred = defer;
}

/**
* @ngdoc object
* @name ui.router.router.$urlRouter
Expand Down Expand Up @@ -242,7 +294,12 @@ function $UrlRouterProvider( $locationProvider, $urlMatcherFactory) {
if (otherwise) check(otherwise);
}

$rootScope.$on('$locationChangeSuccess', update);
function listen() {
listener = listener || $rootScope.$on('$locationChangeSuccess', update);
return listener;
}

if (!interceptDeferred) listen();

return {
/**
Expand Down Expand Up @@ -275,6 +332,10 @@ function $UrlRouterProvider( $locationProvider, $urlMatcherFactory) {
update();
},

listen: function() {
return listen();
},

update: function(read) {
if (read) {
location = $location.url();
Expand Down
73 changes: 53 additions & 20 deletions test/urlRouterSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,40 +2,73 @@ describe("UrlRouter", function () {

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

beforeEach(function() {
angular.module('ui.router.router.test', function() {}).config(function ($urlRouterProvider) {
$urp = $urlRouterProvider;
describe("provider", function () {

$urp.rule(function ($injector, $location) {
var path = $location.path();
if (!/baz/.test(path)) return false;
return path.replace('baz', 'b4z');
}).when('/foo/:param', function($match) {
match = ['/foo/:param', $match];
}).when('/bar', function($match) {
match = ['/bar', $match];
beforeEach(function() {
angular.module('ui.router.router.test', function() {}).config(function ($urlRouterProvider) {
$urlRouterProvider.deferIntercept();
$urp = $urlRouterProvider;
});
});

module('ui.router.router', 'ui.router.router.test');
module('ui.router.router', 'ui.router.router.test');

inject(function($rootScope, $location, $injector) {
scope = $rootScope.$new();
location = $location;
$ur = $injector.invoke($urp.$get);
inject(function($rootScope, $location, $injector) {
scope = $rootScope.$new();
location = $location;
$ur = $injector.invoke($urp.$get);
});
});
});

describe("provider", function () {

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

it("should allow location changes to be deferred", inject(function ($urlRouter, $location, $rootScope) {
var log = [];

$urp.rule(function ($injector, $location) {
log.push($location.path());
});

$location.path("/foo");
$rootScope.$broadcast("$locationChangeSuccess");

expect(log).toEqual([]);

$urlRouter.listen();
$rootScope.$broadcast("$locationChangeSuccess");

expect(log).toEqual(["/foo"]);
}));
});

describe("service", function() {

beforeEach(function() {
angular.module('ui.router.router.test', function() {}).config(function ($urlRouterProvider) {
$urp = $urlRouterProvider;

$urp.rule(function ($injector, $location) {
var path = $location.path();
if (!/baz/.test(path)) return false;
return path.replace('baz', 'b4z');
}).when('/foo/:param', function($match) {
match = ['/foo/:param', $match];
}).when('/bar', function($match) {
match = ['/bar', $match];
});
});

module('ui.router.router', 'ui.router.router.test');

inject(function($rootScope, $location, $injector) {
scope = $rootScope.$new();
location = $location;
$ur = $injector.invoke($urp.$get);
});
});

it("should execute rewrite rules", function () {
location.path("/foo");
scope.$emit("$locationChangeSuccess");
Expand Down

0 comments on commit c72d8ce

Please sign in to comment.