diff --git a/src/urlRouter.js b/src/urlRouter.js index d77c96fbd..bfa22fe0e 100644 --- a/src/urlRouter.js +++ b/src/urlRouter.js @@ -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) { @@ -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 @@ -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"); @@ -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 *
@@ -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)) { @@ -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`. @@ -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 + *+ * 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(); + * }); + *+ * + * @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 @@ -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 { /** @@ -275,6 +332,10 @@ function $UrlRouterProvider( $locationProvider, $urlMatcherFactory) { update(); }, + listen: function() { + return listen(); + }, + update: function(read) { if (read) { location = $location.url(); diff --git a/test/urlRouterSpec.js b/test/urlRouterSpec.js index c296aa75b..e4183d8b2 100644 --- a/test/urlRouterSpec.js +++ b/test/urlRouterSpec.js @@ -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");