Skip to content

Commit b8f0457

Browse files
weingaroMaor Yosef
weingaro
authored and
Maor Yosef
committed
feat($state): added 'state' to state reload method (feat no.1612)
- modiefied options.reload parameter in transitionTo method to accept the following: {reload : true} will reload all states {reload : 'foo.bar'} will reload foo.bar, and any children {reload : stateObj} will reload state found in stateObj, and any children. - added 'state' parameter(string|object) to $state.reload method, to be passed to $state.transitionTo method as the reload option. - test the $state.reload with state parameter
1 parent 3e06565 commit b8f0457

File tree

2 files changed

+159
-5
lines changed

2 files changed

+159
-5
lines changed

src/state.js

+46-4
Original file line numberDiff line numberDiff line change
@@ -820,11 +820,33 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
820820
* });
821821
* </pre>
822822
*
823+
* @param {string=|object=} state - A state name or a state object, which is the root of the resolves to be re-resolved.
824+
* @example
825+
* <pre>
826+
* //assuming app application consists of 3 states: 'contacts', 'contacts.detail', 'contacts.detail.item'
827+
* //and current state is 'contacts.detail.item'
828+
* var app angular.module('app', ['ui.router']);
829+
*
830+
* app.controller('ctrl', function ($scope, $state) {
831+
* $scope.reload = function(){
832+
* //will reload 'contact.detail' and 'contact.detail.item' states
833+
* $state.reload('contact.detail');
834+
* }
835+
* });
836+
* </pre>
837+
*
838+
* `reload()` is just an alias for:
839+
* <pre>
840+
* $state.transitionTo($state.current, $stateParams, {
841+
* reload: true, inherit: false, notify: true
842+
* });
843+
* </pre>
844+
823845
* @returns {promise} A promise representing the state of the new transition. See
824846
* {@link ui.router.state.$state#methods_go $state.go}.
825847
*/
826-
$state.reload = function reload() {
827-
return $state.transitionTo($state.current, $stateParams, { reload: true, inherit: false, notify: true });
848+
$state.reload = function reload(state) {
849+
return $state.transitionTo($state.current, $stateParams, { reload: state || true, inherit: false, notify: true});
828850
};
829851

830852
/**
@@ -928,9 +950,11 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
928950
* - **`relative`** - {object=}, When transitioning with relative path (e.g '^'),
929951
* defines which state to be relative from.
930952
* - **`notify`** - {boolean=true}, If `true` will broadcast $stateChangeStart and $stateChangeSuccess events.
931-
* - **`reload`** (v0.2.5) - {boolean=false}, If `true` will force transition even if the state or params
953+
* - **`reload`** (v0.2.5) - {boolean=false|string=|object=}, If `true` will force transition even if the state or params
932954
* have not changed, aka a reload of the same state. It differs from reloadOnSearch because you'd
933955
* use this when you want to force a reload when *everything* is the same, including search params.
956+
* if String, then will reload the state with the name given in reload, and any children.
957+
* if Object, then a stateObj is expected, will reload the state found in stateObj, and any chhildren.
934958
*
935959
* @returns {promise} A promise representing the state of the new transition. See
936960
* {@link ui.router.state.$state#methods_go $state.go}.
@@ -975,21 +999,39 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
975999

9761000
// Starting from the root of the path, keep all levels that haven't changed
9771001
var keep = 0, state = toPath[keep], locals = root.locals, toLocals = [];
1002+
var skipTriggerReloadCheck = false;
9781003

9791004
if (!options.reload) {
9801005
while (state && state === fromPath[keep] && state.ownParams.$$equals(toParams, fromParams)) {
9811006
locals = toLocals[keep] = state.locals;
9821007
keep++;
9831008
state = toPath[keep];
9841009
}
1010+
} else if (isString(options.reload) || isObject(options.reload)) {
1011+
if (isObject(options.reload) && !options.reload.name) {
1012+
throw new Error('Invalid reload state object');
1013+
}
1014+
1015+
var reloadState = options.reload === true ? fromPath[0] : findState(options.reload);
1016+
if (options.reload && !reloadState) {
1017+
throw new Error("No such reload state '" + (isString(options.reload) ? options.reload : options.reload.name) + "'");
1018+
}
1019+
1020+
skipTriggerReloadCheck = true;
1021+
1022+
while (state && state === fromPath[keep] && state !== reloadState) {
1023+
locals = toLocals[keep] = state.locals;
1024+
keep++;
1025+
state = toPath[keep];
1026+
}
9851027
}
9861028

9871029
// If we're going to the same state and all locals are kept, we've got nothing to do.
9881030
// But clear 'transition', as we still want to cancel any other pending transitions.
9891031
// TODO: We may not want to bump 'transition' if we're called from a location change
9901032
// that we've initiated ourselves, because we might accidentally abort a legitimate
9911033
// transition initiated from code?
992-
if (shouldTriggerReload(to, from, locals, options)) {
1034+
if (!skipTriggerReloadCheck && shouldTriggerReload(to, from, locals, options)) {
9931035
if (to.self.reloadOnSearch !== false) $urlRouter.update();
9941036
$state.transition = null;
9951037
return $q.when($state.current);

test/stateSpec.js

+113-1
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,30 @@ describe('state', function () {
113113
// State param inheritance tests. param1 is inherited by sub1 & sub2;
114114
// param2 should not be transferred (unless explicitly set).
115115
.state('root', { url: '^/root?param1' })
116-
.state('root.sub1', {url: '/1?param2' });
116+
.state('root.sub1', {url: '/1?param2' })
117+
.state('logA', {
118+
url: "/logA",
119+
template: "<div> <div ui-view/></div>",
120+
controller: function() {log += "logA;"}
121+
})
122+
.state('logA.logB', {
123+
url: "/logB",
124+
views:{
125+
'':{
126+
template: "<div> <div ui-view/></div>",
127+
controller: function() {log += "logB;"}
128+
}
129+
}
130+
})
131+
.state('logA.logB.logC', {
132+
url: "/logC",
133+
views:{
134+
'':{
135+
template: "<div> <div ui-view/></div>",
136+
controller: function() {log += "logC;"}
137+
}
138+
}
139+
})
117140
$stateProvider.state('root.sub2', {url: '/2?param2' });
118141

119142
$provide.value('AppInjectable', AppInjectable);
@@ -531,6 +554,92 @@ describe('state', function () {
531554
$q.flush();
532555
expect(log).toBe('Success!controller;Success!controller;');
533556
}));
557+
558+
it('should invoke the controllers by state when given state name', inject(function ($state, $q, $timeout, $rootScope, $compile) {
559+
$compile('<div> <div ui-view/></div>')($rootScope);
560+
$state.transitionTo('logA.logB.logC');
561+
$q.flush();
562+
expect(log).toBe('logA;logB;logC;');
563+
564+
log = '';
565+
$state.reload('logA');
566+
$q.flush();
567+
expect(log).toBe('logA;logB;logC;');
568+
569+
log = '';
570+
$state.reload('logA.logB');
571+
$q.flush();
572+
expect(log).toBe('logB;logC;');
573+
574+
log = '';
575+
$state.reload('logA.logB.logC');
576+
$q.flush();
577+
expect(log).toBe('logC;');
578+
}));
579+
580+
it('should reload all states when passing false', inject(function ($state, $q, $timeout, $rootScope, $compile) {
581+
$compile('<div> <div ui-view/></div>')($rootScope);
582+
$state.transitionTo('logA.logB.logC');
583+
$q.flush();
584+
expect(log).toBe('logA;logB;logC;');
585+
586+
log = '';
587+
$state.reload(false);
588+
$q.flush();
589+
expect(log).toBe('logA;logB;logC;');
590+
}));
591+
592+
it('should reload all states when passing true', inject(function ($state, $q, $timeout, $rootScope, $compile) {
593+
$compile('<div> <div ui-view/></div>')($rootScope);
594+
$state.transitionTo('logA.logB.logC');
595+
$q.flush();
596+
expect(log).toBe('logA;logB;logC;');
597+
598+
log = '';
599+
$state.reload(true);
600+
$q.flush();
601+
expect(log).toBe('logA;logB;logC;');
602+
}));
603+
604+
605+
it('should invoke the controllers by state when given stateObj', inject(function ($state, $q, $timeout, $rootScope, $compile) {
606+
$compile('<div> <div ui-view/></div>')($rootScope);
607+
$state.transitionTo('logA.logB.logC');
608+
609+
$q.flush();
610+
expect(log).toBe('logA;logB;logC;');
611+
612+
log = '';
613+
$state.reload($state.current);
614+
$q.flush();
615+
expect(log).toBe('logC;');
616+
}));
617+
618+
it('should throw an exception for invalid reload state name', inject(function ($state, $q, $timeout, $rootScope, $compile) {
619+
$compile('<div> <div ui-view/></div>')($rootScope);
620+
$state.transitionTo('logA.logB.logC');
621+
$q.flush();
622+
expect(log).toBe('logA;logB;logC;');
623+
624+
expect(function(){
625+
$state.reload('logInvalid')}
626+
).toThrow("No such reload state 'logInvalid'");
627+
}));
628+
629+
it('should throw an exception for invalid reload state object', inject(function ($state, $q, $timeout, $rootScope, $compile) {
630+
$compile('<div> <div ui-view/></div>')($rootScope);
631+
$state.transitionTo('logA.logB.logC');
632+
$q.flush();
633+
expect(log).toBe('logA;logB;logC;');
634+
635+
expect(function(){
636+
$state.reload({foo:'bar'})}
637+
).toThrow("Invalid reload state object");
638+
639+
expect(function(){
640+
$state.reload({name:'invalidState'})}
641+
).toThrow("No such reload state 'invalidState'");
642+
}));
534643
});
535644

536645
describe('.is()', function () {
@@ -784,6 +893,9 @@ describe('state', function () {
784893
'home.item',
785894
'home.redirect',
786895
'json',
896+
'logA',
897+
'logA.logB',
898+
'logA.logB.logC',
787899
'resolveFail',
788900
'resolveTimeout',
789901
'root',

0 commit comments

Comments
 (0)