diff --git a/src/state.js b/src/state.js index 92ec891e9..b4c26ffdf 100644 --- a/src/state.js +++ b/src/state.js @@ -184,12 +184,7 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) { } // Normalize/filter parameters before we pass them to event handlers etc. - var normalizedToParams = {}; - forEach(to.params, function (name) { - var value = toParams[name]; - normalizedToParams[name] = (value != null) ? String(value) : null; - }); - toParams = normalizedToParams; + toParams = normalize(to.params, toParams || {}); // Broadcast start event and cancel the transition if requested if ($rootScope.$broadcast('$stateChangeStart', to.self, toParams, from.self, fromParams) @@ -271,6 +266,11 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) { return $state.$current.includes[findState(stateOrName).name]; }; + $state.href = function (stateOrName, params) { + var state = findState(stateOrName), nav = state.navigable; + if (!nav) throw new Error("State '" + state + "' is not navigable"); + return nav.url.format(normalize(state.params, params || {})); + }; function resolveState(state, params, paramsAreFiltered, inherited, dst) { // We need to track all the promises generated during the resolution process. @@ -345,6 +345,16 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) { }); } + function normalize(keys, values) { + var normalized = {}; + + forEach(keys, function (name) { + var value = values[name]; + normalized[name] = (value != null) ? String(value) : null; + }); + return normalized; + } + function equalForKeys(a, b, keys) { for (var i=0; i<keys.length; i++) { var k = keys[i]; diff --git a/test/stateSpec.js b/test/stateSpec.js index 901a299cb..f9398f42c 100644 --- a/test/stateSpec.js +++ b/test/stateSpec.js @@ -32,7 +32,13 @@ describe('state', function () { .state('C', C) .state('D', D) .state('DD', DD) - .state('E', E); + .state('E', E) + + .state('home', { url: "/" }) + .state('home.item', { url: "front/:id" }) + .state('about', { url: "/about" }) + .state('about.person', { url: "/:person" }) + .state('about.person.item', { url: "/:id" }); $provide.value('AppInjectable', AppInjectable); })); @@ -226,7 +232,7 @@ describe('state', function () { })); }); - + describe('.transition', function () { it('is null when no transition is taking place', inject(function ($state, $q) { expect($state.transition).toBeNull(); @@ -240,4 +246,22 @@ describe('state', function () { expect($state.transition).toBe(trans); })); }); + + + describe('.href()', function () { + it('aborts on un-navigable states', inject(function ($state) { + expect(function() { $state.href("A"); }).toThrow("State 'A' is not navigable"); + })); + + it('generates a URL without parameters', inject(function ($state) { + expect($state.href("home")).toEqual("/"); + expect($state.href("about", {})).toEqual("/about"); + expect($state.href("about", { foo: "bar" })).toEqual("/about"); + })); + + it('generates a URL with parameters', inject(function ($state) { + expect($state.href("about.person", { person: "bob" })).toEqual("/about/bob"); + expect($state.href("about.person.item", { person: "bob", id: null })).toEqual("/about/bob/"); + })); + }); });