Skip to content

Commit

Permalink
feat($state): added 'state' to state reload method (feat no.1612)
Browse files Browse the repository at this point in the history
 - 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
  • Loading branch information
weingaro authored and Maor Yosef committed Mar 29, 2015
1 parent 3e06565 commit b8f0457
Show file tree
Hide file tree
Showing 2 changed files with 159 additions and 5 deletions.
50 changes: 46 additions & 4 deletions src/state.js
Original file line number Diff line number Diff line change
Expand Up @@ -820,11 +820,33 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
* });
* </pre>
*
* @param {string=|object=} state - A state name or a state object, which is the root of the resolves to be re-resolved.
* @example
* <pre>
* //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');
* }
* });
* </pre>
*
* `reload()` is just an alias for:
* <pre>
* $state.transitionTo($state.current, $stateParams, {
* reload: true, inherit: false, notify: true
* });
* </pre>
* @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: state || true, inherit: false, notify: true});
};

/**
Expand Down Expand Up @@ -928,9 +950,11 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
* - **`relative`** - {object=}, When transitioning with relative path (e.g '^'),
* defines which state to be relative from.
* - **`notify`** - {boolean=true}, If `true` will broadcast $stateChangeStart and $stateChangeSuccess events.
* - **`reload`** (v0.2.5) - {boolean=false}, If `true` will force transition even if the state or params
* - **`reload`** (v0.2.5) - {boolean=false|string=|object=}, 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.
* if String, then will reload the state with the name given in reload, and any children.
* if Object, then a stateObj is expected, will reload the state found in stateObj, and any chhildren.
*
* @returns {promise} A promise representing the state of the new transition. See
* {@link ui.router.state.$state#methods_go $state.go}.
Expand Down Expand Up @@ -975,21 +999,39 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {

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

if (!options.reload) {
while (state && state === fromPath[keep] && state.ownParams.$$equals(toParams, fromParams)) {
locals = toLocals[keep] = state.locals;
keep++;
state = toPath[keep];
}
} else if (isString(options.reload) || isObject(options.reload)) {
if (isObject(options.reload) && !options.reload.name) {
throw new Error('Invalid reload state object');
}

var reloadState = options.reload === true ? fromPath[0] : findState(options.reload);
if (options.reload && !reloadState) {
throw new Error("No such reload state '" + (isString(options.reload) ? options.reload : options.reload.name) + "'");
}

skipTriggerReloadCheck = true;

while (state && state === fromPath[keep] && state !== reloadState) {
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.
// But clear 'transition', as we still want to cancel any other pending transitions.
// 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 (!skipTriggerReloadCheck && shouldTriggerReload(to, from, locals, options)) {
if (to.self.reloadOnSearch !== false) $urlRouter.update();
$state.transition = null;
return $q.when($state.current);
Expand Down
114 changes: 113 additions & 1 deletion test/stateSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: "<div> <div ui-view/></div>",
controller: function() {log += "logA;"}
})
.state('logA.logB', {
url: "/logB",
views:{
'':{
template: "<div> <div ui-view/></div>",
controller: function() {log += "logB;"}
}
}
})
.state('logA.logB.logC', {
url: "/logC",
views:{
'':{
template: "<div> <div ui-view/></div>",
controller: function() {log += "logC;"}
}
}
})
$stateProvider.state('root.sub2', {url: '/2?param2' });

$provide.value('AppInjectable', AppInjectable);
Expand Down Expand Up @@ -531,6 +554,92 @@ describe('state', function () {
$q.flush();
expect(log).toBe('Success!controller;Success!controller;');
}));

it('should invoke the controllers by state when given state name', inject(function ($state, $q, $timeout, $rootScope, $compile) {
$compile('<div> <div ui-view/></div>')($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 reload all states when passing false', inject(function ($state, $q, $timeout, $rootScope, $compile) {
$compile('<div> <div ui-view/></div>')($rootScope);
$state.transitionTo('logA.logB.logC');
$q.flush();
expect(log).toBe('logA;logB;logC;');

log = '';
$state.reload(false);
$q.flush();
expect(log).toBe('logA;logB;logC;');
}));

it('should reload all states when passing true', inject(function ($state, $q, $timeout, $rootScope, $compile) {
$compile('<div> <div ui-view/></div>')($rootScope);
$state.transitionTo('logA.logB.logC');
$q.flush();
expect(log).toBe('logA;logB;logC;');

log = '';
$state.reload(true);
$q.flush();
expect(log).toBe('logA;logB;logC;');
}));


it('should invoke the controllers by state when given stateObj', inject(function ($state, $q, $timeout, $rootScope, $compile) {
$compile('<div> <div ui-view/></div>')($rootScope);
$state.transitionTo('logA.logB.logC');

$q.flush();
expect(log).toBe('logA;logB;logC;');

log = '';
$state.reload($state.current);
$q.flush();
expect(log).toBe('logC;');
}));

it('should throw an exception for invalid reload state name', inject(function ($state, $q, $timeout, $rootScope, $compile) {
$compile('<div> <div ui-view/></div>')($rootScope);
$state.transitionTo('logA.logB.logC');
$q.flush();
expect(log).toBe('logA;logB;logC;');

expect(function(){
$state.reload('logInvalid')}
).toThrow("No such reload state 'logInvalid'");
}));

it('should throw an exception for invalid reload state object', inject(function ($state, $q, $timeout, $rootScope, $compile) {
$compile('<div> <div ui-view/></div>')($rootScope);
$state.transitionTo('logA.logB.logC');
$q.flush();
expect(log).toBe('logA;logB;logC;');

expect(function(){
$state.reload({foo:'bar'})}
).toThrow("Invalid reload state object");

expect(function(){
$state.reload({name:'invalidState'})}
).toThrow("No such reload state 'invalidState'");
}));
});

describe('.is()', function () {
Expand Down Expand Up @@ -784,6 +893,9 @@ describe('state', function () {
'home.item',
'home.redirect',
'json',
'logA',
'logA.logB',
'logA.logB.logC',
'resolveFail',
'resolveTimeout',
'root',
Expand Down

0 comments on commit b8f0457

Please sign in to comment.