Skip to content

Commit 6ca0d77

Browse files
Matt Ginztonchristopherthielen
Matt Ginzton
authored andcommitted
fix($state): reloadOnSearch should not affect non-search param changes.
The handling of `reloadOnSearch: false` caused ui-router to avoid reloading the state in more cases than it should. The designed and documented behavior of this flag is to avoid state reload when the URL search string changes. It was also avoiding state reload when the URL path (or any non-search parameters to the state) changed, and even when state reload was explicitly requested. This change - flips the name of shouldTriggerReload (and the accompanying guard boolean, skipTriggerReloadCheck) to match the direction of the logic: shouldSkipReload and allowSkipReloadCheck - teaches shouldSkipReload to look at the types of the differing parameters, and only skip the reload if the only parameters that differ were search parameters - pulls the test for options.reload to the front of the complex boolean expression. - Updates $state.params and $stateParams when skipping reload Fixes #1079. Helps with one of the cases broken in #582.
1 parent 22a2b10 commit 6ca0d77

File tree

2 files changed

+54
-8
lines changed

2 files changed

+54
-8
lines changed

src/state.js

+25-7
Original file line numberDiff line numberDiff line change
@@ -954,7 +954,7 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
954954
* have not changed, aka a reload of the same state. It differs from reloadOnSearch because you'd
955955
* use this when you want to force a reload when *everything* is the same, including search params.
956956
* 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.
957+
* if Object, then a stateObj is expected, will reload the state found in stateObj, and any children.
958958
*
959959
* @returns {promise} A promise representing the state of the new transition. See
960960
* {@link ui.router.state.$state#methods_go $state.go}.
@@ -1002,7 +1002,6 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
10021002

10031003
// Starting from the root of the path, keep all levels that haven't changed
10041004
var keep = 0, state = toPath[keep], locals = root.locals, toLocals = [];
1005-
var skipTriggerReloadCheck = false;
10061005

10071006
if (!options.reload) {
10081007
while (state && state === fromPath[keep] && state.ownParams.$$equals(toParams, fromParams)) {
@@ -1020,8 +1019,6 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
10201019
throw new Error("No such reload state '" + (isString(options.reload) ? options.reload : options.reload.name) + "'");
10211020
}
10221021

1023-
skipTriggerReloadCheck = true;
1024-
10251022
while (state && state === fromPath[keep] && state !== reloadState) {
10261023
locals = toLocals[keep] = state.locals;
10271024
keep++;
@@ -1034,8 +1031,10 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
10341031
// TODO: We may not want to bump 'transition' if we're called from a location change
10351032
// that we've initiated ourselves, because we might accidentally abort a legitimate
10361033
// transition initiated from code?
1037-
if (!skipTriggerReloadCheck && shouldTriggerReload(to, from, locals, options)) {
1034+
if (shouldSkipReload(to, toParams, from, fromParams, locals, options)) {
10381035
if (to.self.reloadOnSearch !== false) $urlRouter.update();
1036+
$state.params = toParams;
1037+
copy($state.params, $stateParams);
10391038
$state.transition = null;
10401039
return $q.when($state.current);
10411040
}
@@ -1429,8 +1428,27 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
14291428
return $state;
14301429
}
14311430

1432-
function shouldTriggerReload(to, from, locals, options) {
1433-
if (to === from && ((locals === from.locals && !options.reload) || (to.self.reloadOnSearch === false))) {
1431+
function shouldSkipReload(to, toParams, from, fromParams, locals, options) {
1432+
// Return true if there are no differences in non-search (path/object) params, false if there are differences
1433+
function nonSearchParamsEqual(fromAndToState, fromParams, toParams) {
1434+
// Identify whether all the parameters that differ between `fromParams` and `toParams` were search params.
1435+
function notSearchParam(key) {
1436+
return fromAndToState.params[key].location != "search";
1437+
}
1438+
var nonQueryParamKeys = fromAndToState.params.$$keys().filter(notSearchParam);
1439+
var nonQueryParams = pick.apply(this, [fromAndToState.params].concat(nonQueryParamKeys));
1440+
var nonQueryParamSet = new $$UMFP.ParamSet(nonQueryParams);
1441+
return nonQueryParamSet.$$equals(fromParams, toParams)
1442+
}
1443+
1444+
// If reload was not explicitly requested
1445+
// and we're transitioning to the same state we're already in
1446+
// and the locals didn't change
1447+
// or they changed in a way that doesn't merit reloading
1448+
// (reloadOnParams:false, or reloadOnSearch.false and only search params changed)
1449+
// Then return true.
1450+
if (!options.reload && to === from &&
1451+
(locals === from.locals || (to.self.reloadOnSearch === false && nonSearchParamsEqual(from, fromParams, toParams)))) {
14341452
return true;
14351453
}
14361454
}

test/stateSpec.js

+29-1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ describe('state', function () {
2929
HH = { parent: H },
3030
HHH = {parent: HH, data: {propA: 'overriddenA', propC: 'propC'} },
3131
RS = { url: '^/search?term', reloadOnSearch: false },
32+
RSP = { url: '^/:doReload/search?term', reloadOnSearch: false },
3233
OPT = { url: '/opt/:param', params: { param: "100" } },
3334
OPT2 = { url: '/opt2/:param2/:param3', params: { param3: "300", param4: "400" } },
3435
AppInjectable = {};
@@ -55,6 +56,7 @@ describe('state', function () {
5556
.state('OPT', OPT)
5657
.state('OPT.OPT2', OPT2)
5758
.state('RS', RS)
59+
.state('RSP', RSP)
5860

5961
.state('home', { url: "/" })
6062
.state('home.item', { url: "front/:id" })
@@ -212,7 +214,32 @@ describe('state', function () {
212214
});
213215
$q.flush();
214216
expect($location.search()).toEqual({term: 'hello'});
215-
expect(called).toBeFalsy();
217+
expect(called).toBeFalsy();
218+
}));
219+
220+
it('updates $stateParams when state.reloadOnSearch=false, and only query params changed', inject(function ($state, $stateParams, $q, $location, $rootScope){
221+
initStateTo(RS);
222+
$location.search({term: 'hello'});
223+
var called;
224+
$rootScope.$on('$stateChangeStart', function (ev, to, toParams, from, fromParams) {
225+
called = true
226+
});
227+
$q.flush();
228+
expect($stateParams).toEqual({term: 'hello'});
229+
expect(called).toBeFalsy();
230+
}));
231+
232+
it('does trigger state change for path params even if reloadOnSearch is false', inject(function ($state, $q, $location, $rootScope){
233+
initStateTo(RSP, { doReload: 'foo' });
234+
expect($state.params.doReload).toEqual('foo');
235+
var called;
236+
$rootScope.$on('$stateChangeStart', function (ev, to, toParams, from, fromParams) {
237+
called = true
238+
});
239+
$state.transitionTo(RSP, { doReload: 'bar' });
240+
$q.flush();
241+
expect($state.params.doReload).toEqual('bar');
242+
expect(called).toBeTruthy();
216243
}));
217244

218245
it('ignores non-applicable state parameters', inject(function ($state, $q) {
@@ -940,6 +967,7 @@ describe('state', function () {
940967
'OPT',
941968
'OPT.OPT2',
942969
'RS',
970+
'RSP',
943971
'about',
944972
'about.person',
945973
'about.person.item',

0 commit comments

Comments
 (0)