diff --git a/src/state.js b/src/state.js index 730e719f8..6b98e250b 100644 --- a/src/state.js +++ b/src/state.js @@ -820,11 +820,33 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) { * }); * * + * @param {string} state A state name which is the root of the resolves to re-resolved. + * @example + *
+     * //assuming app application consists of 3 states: 'contacts', 'contacts.detail', 'contacts.detail.item' 
+     * //and current state is 'contacts.detail.item'
+     * var app angular.module('app', ['ui.router']);
+     *
+     * app.controller('ctrl', function ($scope, $state) {
+     *   $scope.reload = function(){
+     *     //will reload 'contact.detail' and 'contact.detail.item' states
+     *     $state.reload('contact.detail');
+     *   }
+     * });
+     * 
+ * + * `reload()` is just an alias for: + *
+     * $state.transitionTo($state.current, $stateParams, { 
+     *   reload: true, inherit: false, notify: true
+     * });
+     * 
+ * @returns {promise} A promise representing the state of the new transition. See * {@link ui.router.state.$state#methods_go $state.go}. */ - $state.reload = function reload() { - return $state.transitionTo($state.current, $stateParams, { reload: true, inherit: false, notify: true }); + $state.reload = function reload(state) { + return $state.transitionTo($state.current, $stateParams, { reload: true, inherit: false, notify: true, reloadState: state }); }; /** @@ -931,6 +953,8 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) { * - **`reload`** (v0.2.5) - {boolean=false}, If `true` will force transition even if the state or params * have not changed, aka a reload of the same state. It differs from reloadOnSearch because you'd * use this when you want to force a reload when *everything* is the same, including search params. + * - **'reloadState'** - {string=null}, A state name which will be the root state to reload from. + * should be used with options.reload=true * * @returns {promise} A promise representing the state of the new transition. See * {@link ui.router.state.$state#methods_go $state.go}. @@ -938,7 +962,7 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) { $state.transitionTo = function transitionTo(to, toParams, options) { toParams = toParams || {}; options = extend({ - location: true, inherit: false, relative: null, notify: true, reload: false, $retry: false + location: true, inherit: false, relative: null, notify: true, reload: false, $retry: false, reloadState : null }, options || {}); var from = $state.$current, fromParams = $state.params, fromPath = from.path; @@ -982,6 +1006,16 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) { keep++; state = toPath[keep]; } + } else if (options.reloadState) { + if (!isDefined(findState(options.reloadState))) { + throw new Error("No such state '" + options.reloadState + "'"); + } + + while (state && state === fromPath[keep] && state.toString().toLowerCase() !== options.reloadState.toLowerCase()) { + locals = toLocals[keep] = state.locals; + keep++; + state = toPath[keep]; + } } // If we're going to the same state and all locals are kept, we've got nothing to do. @@ -989,7 +1023,7 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) { // TODO: We may not want to bump 'transition' if we're called from a location change // that we've initiated ourselves, because we might accidentally abort a legitimate // transition initiated from code? - if (shouldTriggerReload(to, from, locals, options)) { + if (!options.reloadState && shouldTriggerReload(to, from, locals, options)) { if (to.self.reloadOnSearch !== false) $urlRouter.update(); $state.transition = null; return $q.when($state.current); diff --git a/test/stateSpec.js b/test/stateSpec.js index 77c9373c6..49e36edae 100644 --- a/test/stateSpec.js +++ b/test/stateSpec.js @@ -113,7 +113,30 @@ describe('state', function () { // State param inheritance tests. param1 is inherited by sub1 & sub2; // param2 should not be transferred (unless explicitly set). .state('root', { url: '^/root?param1' }) - .state('root.sub1', {url: '/1?param2' }); + .state('root.sub1', {url: '/1?param2' }) + .state('logA', { + url: "/logA", + template: "
", + controller: function() {log += "logA;"} + }) + .state('logA.logB', { + url: "/logB", + views:{ + '':{ + template: "
", + controller: function() {log += "logB;"} + } + } + }) + .state('logA.logB.logC', { + url: "/logC", + views:{ + '':{ + template: "
", + controller: function() {log += "logC;"} + } + } + }) $stateProvider.state('root.sub2', {url: '/2?param2' }); $provide.value('AppInjectable', AppInjectable); @@ -531,6 +554,40 @@ describe('state', function () { $q.flush(); expect(log).toBe('Success!controller;Success!controller;'); })); + + it('should invoke the controllers by state', inject(function ($state, $q, $timeout, $rootScope, $compile) { + $compile('
')($rootScope); + $state.transitionTo('logA.logB.logC'); + $q.flush(); + expect(log).toBe('logA;logB;logC;'); + + log = ''; + $state.reload('logA'); + $q.flush(); + expect(log).toBe('logA;logB;logC;'); + + log = ''; + $state.reload('logA.logB'); + $q.flush(); + expect(log).toBe('logB;logC;'); + + log = ''; + $state.reload('logA.logB.logC'); + $q.flush(); + expect(log).toBe('logC;'); + + })); + + it('should throw an exception for invalid reload state', inject(function ($state, $q, $timeout, $rootScope, $compile) { + $compile('
')($rootScope); + $state.transitionTo('logA.logB.logC'); + $q.flush(); + expect(log).toBe('logA;logB;logC;'); + + expect(function(){ + $state.reload('logInvalid')} + ).toThrow("No such state 'logInvalid'"); + })); }); describe('.is()', function () { @@ -784,6 +841,9 @@ describe('state', function () { 'home.item', 'home.redirect', 'json', + 'logA', + 'logA.logB', + 'logA.logB.logC', 'resolveFail', 'resolveTimeout', 'root',