Skip to content

Adding 'reloadOnPath' and 'reloadOnQuery' configuration to the state. #1037

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 56 additions & 1 deletion src/state.js
Original file line number Diff line number Diff line change
Expand Up @@ -749,14 +749,20 @@ 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.
* - **`reloadOnPath`** (v0.2.10)- {boolean=true}, If `false` controllers and views will not be reloaded when only the path params change.
* 'reloadOnQuery' value of 'true' with search params changed will override this.
* In the event of reload is suppressed, the path params of url will still change.
* - **`reloadOnQuery`** (v0.2.10)- {boolean=true}, If `false` controllers and views will not be reloaded when only the search params change.
* 'reloadOnPath' value of 'true' with path params changed will override this.
* In the event of reload is suppressed, the query string params of the url will still change.
*
* @returns {promise} A promise representing the state of the new transition. See
* {@link ui.router.state.$state#methods_go $state.go}.
*/
$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, reloadOnPath: true, reloadOnQuery: true, $retry: false
}, options || {});

var from = $state.$current, fromParams = $state.params, fromPath = from.path;
Expand Down Expand Up @@ -813,6 +819,55 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
// Filter parameters before we pass them to event handlers etc.
toParams = filterByKeys(objectKeys(to.params), toParams || {});


// If we're going to the same state
// Let's handle the reloadOnPath and reloadOnQuery state configurations
if ( to === from ) {
var key, param, shouldNotReload, currentLocals,
isPathChanged = false,
isSearchChanged = false,
// Is the reloadOnPath set on the state or the options?
reloadOnPath = !(to.self.reloadOnPath === false || options.reloadOnPath === false),
// Is the reloadOnQuery set on the state or the options?
reloadOnQuery = !(to.self.reloadOnQuery === false || options.reloadOnQuery === false);

// If either reloadOnPath or reloadOnQuery is suppressed let's flag whether params of either type is changed
if(!reloadOnPath || !reloadOnQuery) {
for(key in to.params) {
param = to.params[key];
if(toParams[key]!==$stateParams[key]) {
if(param.paramType=='path')
isPathChanged = true;
else if (param.paramType=='query')
isSearchChanged = true;
}
}
}

// We should not reload if both reloadOnPath and reloadOnQuery is suppressed,
// reloadOnPath is suppressed and search parameters haven't changed, or
// reloadOnQuery is suppressed and path parameters haven't changed
shouldNotReload = ((!reloadOnPath && !reloadOnQuery) ||
(!reloadOnPath && !isSearchChanged) || (!reloadOnQuery && !isPathChanged));
if(shouldNotReload) {
//update the relevant parameters to reflect changes to the current state's $stateParams
$state.params = toParams;
copy($state.params, $stateParams);
currentLocals = to.locals;
copy($state.params, currentLocals.globals.$stateParams);
copy($state.params, currentLocals['@'].$stateParams);
if(currentLocals['@'].$$controller)
copy($state.params, currentLocals['@'].$$controller.$stateParams);
if(currentLocals.$$controller)
copy($state.params, currentLocals.$$controller.$stateParams);
$urlRouter.push(to.url, $stateParams);
$urlRouter.update(true);
$urlRouter.sync();
$state.transition = null;
return $q.when($state.current);
}
}

// Broadcast start event and cancel the transition if requested
if (options.notify) {
/**
Expand Down
8 changes: 4 additions & 4 deletions src/urlMatcherFactory.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,10 @@ function UrlMatcher(pattern, config) {
return isDefined(value) ? this.type.decode(value) : $UrlMatcherFactory.$$getDefaultValue(this);
}

function addParameter(id, type, config) {
function addParameter(id, type, config, paramType) {
if (!/^\w+(-+\w+)*$/.test(id)) throw new Error("Invalid parameter name '" + id + "' in pattern '" + pattern + "'");
if (params[id]) throw new Error("Duplicate parameter name '" + id + "' in pattern '" + pattern + "'");
params[id] = extend({ type: type || new Type(), $value: $value }, config);
params[id] = extend({ type: type || new Type(), $value: $value , paramType: paramType}, config);
}

function quoteRegExp(string, pattern, isOptional) {
Expand Down Expand Up @@ -124,7 +124,7 @@ function UrlMatcher(pattern, config) {
if (segment.indexOf('?') >= 0) break; // we're into the search part

compiled += quoteRegExp(segment, type.$subPattern(), isDefined(cfg.value));
addParameter(id, type, cfg);
addParameter(id, type, cfg, 'path');
segments.push(segment);
last = placeholder.lastIndex;
}
Expand All @@ -140,7 +140,7 @@ function UrlMatcher(pattern, config) {

// Allow parameters to be separated by '?' as well as '&' to make concat() easier
forEach(search.substring(1).split(/[&?]/), function(key) {
addParameter(key, null, paramConfig(key));
addParameter(key, null, paramConfig(key), 'query');
});
} else {
this.sourcePath = pattern;
Expand Down
64 changes: 63 additions & 1 deletion test/stateSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ describe('state', function () {
HH = { parent: H },
HHH = {parent: HH, data: {propA: 'overriddenA', propC: 'propC'} },
RS = { url: '^/search?term', reloadOnSearch: false },
RP = { url: '/path/:path?term', reloadOnPath: false },
RQ = { url: '/path2/:path?term', reloadOnQuery: false },
RPRQ = { url: '/path3/:path?term', reloadOnPath: false, reloadOnQuery: false },
AppInjectable = {};

beforeEach(module(function ($stateProvider, $provide) {
Expand All @@ -47,6 +50,9 @@ describe('state', function () {
.state('HH', HH)
.state('HHH', HHH)
.state('RS', RS)
.state('RP', RP)
.state('RQ', RQ)
.state('RPRQ', RPRQ)

.state('home', { url: "/" })
.state('home.item', { url: "front/:id" })
Expand Down Expand Up @@ -161,7 +167,60 @@ describe('state', function () {
});
$q.flush();
expect($location.search()).toEqual({term: 'hello'});
expect(called).toBeFalsy();
expect(called).toBeFalsy();
}));

it('doesn\'t trigger state change and does change $stateParams if reloadOnPath is false',
inject(function ($state, $stateParams, $q, $location, $rootScope){
initStateTo(RP, { path: 'hello'});
var called;
$rootScope.$on('$stateChangeStart', function (ev, to, toParams, from, fromParams) {
called = true
});
$state.go('RP', { path: 'world' });
$q.flush();
expect($location.path()).toEqual('/path/world');
expect(called).toBeFalsy();
expect($stateParams).toEqual({ path: 'world', term: undefined });
$state.go('RP', { path: 'hello', term: 'term' }); // test when search params change
$q.flush();
expect(called).toBeTruthy();
expect($location.path()).toEqual('/path/hello');
expect($stateParams).toEqual({ path: 'hello', term: 'term' });
}));

it('doesn\'t trigger state change and does change $stateParams if reloadOnQuery is false',
inject(function ($state, $stateParams, $q, $location, $rootScope){
initStateTo(RQ, { path: 'hello', term: 'term'});
var called;
$rootScope.$on('$stateChangeStart', function (ev, to, toParams, from, fromParams) {
called = true
});
$state.go('RQ', { term: 'newTerm' });
$q.flush();
expect($location.search()).toEqual({ term: 'newTerm' });
expect(called).toBeFalsy();
expect($stateParams).toEqual({ path: 'hello', term: 'newTerm' });
$state.go('RQ', { path: 'world', term: 'term'}); // test when path params change
$q.flush();
expect(called).toBeTruthy();
expect($location.search()).toEqual({ term: 'term' });
expect($stateParams).toEqual({ path: 'world', term: 'term' });
}));

it('doesn\'t trigger state change and does change $stateParams if reloadOnPath and reloadOnQuery is false',
inject(function ($state, $stateParams, $q, $location, $rootScope){
initStateTo(RPRQ, { path: 'hello', term: 'term'});
var called;
$rootScope.$on('$stateChangeStart', function (ev, to, toParams, from, fromParams) {
called = true
});
$state.go('RPRQ', {}); // visit same state with same params
$q.flush();
expect($location.search()).toEqual({ term: 'term' });
expect($location.path()).toEqual('/path3/hello');
expect(called).toBeFalsy();
expect($stateParams).toEqual({ path: 'hello', term: 'term' });
}));

it('ignores non-applicable state parameters', inject(function ($state, $q) {
Expand Down Expand Up @@ -709,6 +768,9 @@ describe('state', function () {
'H',
'HH',
'HHH',
'RP',
'RPRQ',
'RQ',
'RS',
'about',
'about.person',
Expand Down