Skip to content

Commit

Permalink
BC-BREAK: Change Transition.previous() to Transition.redirectedFrom()
Browse files Browse the repository at this point in the history
refactor(Transition): BC-BREAK: Change Transition.previous() to Transition.redirectedFrom()
- redirectedFrom is more accurate

fix(redirect): Allow URL-sync() redirect targets to update the URL
- A URL sync uses { location: false }.  When a transition redirects a URL sync, the options are retained.
- Now URL sync uses { source: 'url' } and redirects override that with { source: 'redirect' }.
   When synchronizing URL, either { source: 'url' } or { location: false } skips url update.

Closes #2966
  • Loading branch information
christopherthielen committed Sep 4, 2016
1 parent bc17066 commit 82b9491
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 9 deletions.
6 changes: 5 additions & 1 deletion src/hooks/url.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ const updateUrl: TransitionHookFn = (transition: Transition) => {
let $state: StateService = transition.router.stateService;
let $urlRouter: UrlRouter = transition.router.urlRouter;

if (options.location && $state.$current.navigable) {
// Dont update the url in these situations:
// The transition was triggered by a URL sync (options.source === 'url')
// The user doesn't want the url to update (options.location === false)
// The destination state, and all parents have no navigable url
if (options.source !== 'url' && options.location && $state.$current.navigable) {
var urlOptions = {replace: options.location === 'replace'};
$urlRouter.push($state.$current.navigable.url, $state.params, urlOptions);
}
Expand Down
2 changes: 1 addition & 1 deletion src/state/stateQueueManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export class StateQueueManager {

$urlRouterProvider.when(state.url, ['$match', '$stateParams', function ($match: RawParams, $stateParams: RawParams) {
if ($state.$current.navigable !== state || !equalForKeys($match, $stateParams)) {
$state.transitionTo(state, $match, { inherit: true, location: false, source: "url" });
$state.transitionTo(state, $match, { inherit: true, source: "url" });
}
}], (rule) => state._urlRule = rule);
}
Expand Down
8 changes: 5 additions & 3 deletions src/transition/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,14 @@ export interface TransitionOptions {
custom ?: any;
/** @hidden @internal */
reloadState ?: (State);
/** @hidden @internal */
previous ?: Transition;
/** @hidden @internal
* If this transition is a redirect, this property should be the original Transition (which was redirected to this one)
*/
redirectedFrom?: Transition;
/** @hidden @internal */
current ?: () => Transition;
/** @hidden @internal */
source ?: "sref"|"url"|"unknown";
source ?: "sref"|"url"|"redirect"|"unknown";
}

/** @hidden @internal */
Expand Down
22 changes: 18 additions & 4 deletions src/transition/transition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -297,12 +297,26 @@ export class Transition implements IHookRegistry {
}

/**
* Gets the previous transition, from which this transition was redirected.
* If the current transition is a redirect, returns the transition that was redirected.
*
* Gets the transition from which this transition was redirected.
*
*
* @example
* ```js
*
* let transitionA = $state.go('A').transitionA
* transitionA.onStart({}, () => $state.target('B'));
* $transitions.onSuccess({ to: 'B' }, (trans) => {
* trans.to().name === 'B'; // true
* trans.redirectedFrom() === transitionA; // true
* });
* ```
*
* @returns The previous Transition, or null if this Transition is not the result of a redirection
*/
previous(): Transition {
return this._options.previous || null;
redirectedFrom(): Transition {
return this._options.redirectedFrom || null;
}

/**
Expand Down Expand Up @@ -371,7 +385,7 @@ export class Transition implements IHookRegistry {
* @returns Returns a new [[Transition]] instance.
*/
redirect(targetState: TargetState): Transition {
let newOptions = extend({}, this.options(), targetState.options(), { previous: this });
let newOptions = extend({}, this.options(), targetState.options(), { redirectedFrom: this, source: "redirect" });
targetState = new TargetState(targetState.identifier(), targetState.$state(), targetState.params(), newOptions);

let newTransition = this.router.transitionService.create(this._treeChanges.from, targetState);
Expand Down
95 changes: 95 additions & 0 deletions test/core/stateServiceSpec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { UIRouter
TransitionService,
StateService,
} from "../../src/core";
import "../../src/justjs";
import {tree2Array} from "../testUtils.ts";
import {TransitionOptions} from "../../src/transition/interface";
import {LocationServices, services} from "../../src/common/coreservices";

describe('stateService', function () {
let router: UIRouter;
let $transitions: TransitionService;
let $state: StateService;
let $loc: LocationServices;

const wait = (val?) =>
new Promise((resolve) => setTimeout(() => resolve(val)));

beforeEach(() => {
router = new UIRouter();
$loc = services.location;
$state = router.stateService;
$transitions = router.transitionService;
router.stateRegistry.stateQueue.autoFlush($state);

var stateTree = {
first: {},
second: {},
third: {},
A: {
url: '/a',
B: {
url: '/b',
C: {
url: '/c',
D: {
url: '/d'
}
}
}
}
};

let states = tree2Array(stateTree, false);
states.forEach(state => router.stateRegistry.register(state));
});

describe('transitionTo', () => {

it("should handle redirects", ((done) => {
$transitions.onStart({ to: 'D'}, trans => (log.push('redirect'), trans.router.stateService.target('C')));
$transitions.onStart({ to: 'C'}, trans => { cOpts = trans.options(); });

var log = [], transition = $state.go("D").transition;
var cOpts: TransitionOptions = {};

wait().then(() => {
expect(log).toEqual(['redirect']);
expect(cOpts.redirectedFrom).toBe(transition);
expect(cOpts.source).toBe("redirect");
})
.then(done, done);
}));

it("should not update the URL in response to synchronizing URL", ((done) => {
$loc.url('/a/b/c');
spyOn($loc, 'url').and.callThrough();
router.urlRouter.sync();

wait().then(() => {
expect($state.current.name).toBe('C');
let pushedUrls = $loc.url.calls.all().map(x => x.args[0]).filter(x => x !== undefined);
expect(pushedUrls).toEqual([]);
expect($loc.path()).toBe('/a/b/c');
done();
})
}));

it("should update the URL in response to synchronizing URL then redirecting", ((done) => {
$transitions.onStart({ to: 'C' }, () => $state.target('D'));

$loc.url('/a/b/c');
spyOn($loc, 'url').and.callThrough();
router.urlRouter.sync();

wait().then(() => {
expect($state.current.name).toBe('D');
let pushedUrls = $loc.url.calls.all().map(x => x.args[0]).filter(x => x !== undefined);
expect(pushedUrls).toEqual(['/a/b/c/d']);
expect($loc.path()).toBe('/a/b/c/d');
done();
})
}));
});
});

0 comments on commit 82b9491

Please sign in to comment.