Skip to content

Commit 82b9491

Browse files
BC-BREAK: Change Transition.previous() to Transition.redirectedFrom()
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
1 parent bc17066 commit 82b9491

File tree

5 files changed

+124
-9
lines changed

5 files changed

+124
-9
lines changed

src/hooks/url.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@ const updateUrl: TransitionHookFn = (transition: Transition) => {
1515
let $state: StateService = transition.router.stateService;
1616
let $urlRouter: UrlRouter = transition.router.urlRouter;
1717

18-
if (options.location && $state.$current.navigable) {
18+
// Dont update the url in these situations:
19+
// The transition was triggered by a URL sync (options.source === 'url')
20+
// The user doesn't want the url to update (options.location === false)
21+
// The destination state, and all parents have no navigable url
22+
if (options.source !== 'url' && options.location && $state.$current.navigable) {
1923
var urlOptions = {replace: options.location === 'replace'};
2024
$urlRouter.push($state.$current.navigable.url, $state.params, urlOptions);
2125
}

src/state/stateQueueManager.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ export class StateQueueManager {
9696

9797
$urlRouterProvider.when(state.url, ['$match', '$stateParams', function ($match: RawParams, $stateParams: RawParams) {
9898
if ($state.$current.navigable !== state || !equalForKeys($match, $stateParams)) {
99-
$state.transitionTo(state, $match, { inherit: true, location: false, source: "url" });
99+
$state.transitionTo(state, $match, { inherit: true, source: "url" });
100100
}
101101
}], (rule) => state._urlRule = rule);
102102
}

src/transition/interface.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,12 +75,14 @@ export interface TransitionOptions {
7575
custom ?: any;
7676
/** @hidden @internal */
7777
reloadState ?: (State);
78-
/** @hidden @internal */
79-
previous ?: Transition;
78+
/** @hidden @internal
79+
* If this transition is a redirect, this property should be the original Transition (which was redirected to this one)
80+
*/
81+
redirectedFrom?: Transition;
8082
/** @hidden @internal */
8183
current ?: () => Transition;
8284
/** @hidden @internal */
83-
source ?: "sref"|"url"|"unknown";
85+
source ?: "sref"|"url"|"redirect"|"unknown";
8486
}
8587

8688
/** @hidden @internal */

src/transition/transition.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -297,12 +297,26 @@ export class Transition implements IHookRegistry {
297297
}
298298

299299
/**
300-
* Gets the previous transition, from which this transition was redirected.
300+
* If the current transition is a redirect, returns the transition that was redirected.
301+
*
302+
* Gets the transition from which this transition was redirected.
303+
*
304+
*
305+
* @example
306+
* ```js
307+
*
308+
* let transitionA = $state.go('A').transitionA
309+
* transitionA.onStart({}, () => $state.target('B'));
310+
* $transitions.onSuccess({ to: 'B' }, (trans) => {
311+
* trans.to().name === 'B'; // true
312+
* trans.redirectedFrom() === transitionA; // true
313+
* });
314+
* ```
301315
*
302316
* @returns The previous Transition, or null if this Transition is not the result of a redirection
303317
*/
304-
previous(): Transition {
305-
return this._options.previous || null;
318+
redirectedFrom(): Transition {
319+
return this._options.redirectedFrom || null;
306320
}
307321

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

377391
let newTransition = this.router.transitionService.create(this._treeChanges.from, targetState);

test/core/stateServiceSpec.ts

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { UIRouter
2+
TransitionService,
3+
StateService,
4+
} from "../../src/core";
5+
import "../../src/justjs";
6+
import {tree2Array} from "../testUtils.ts";
7+
import {TransitionOptions} from "../../src/transition/interface";
8+
import {LocationServices, services} from "../../src/common/coreservices";
9+
10+
describe('stateService', function () {
11+
let router: UIRouter;
12+
let $transitions: TransitionService;
13+
let $state: StateService;
14+
let $loc: LocationServices;
15+
16+
const wait = (val?) =>
17+
new Promise((resolve) => setTimeout(() => resolve(val)));
18+
19+
beforeEach(() => {
20+
router = new UIRouter();
21+
$loc = services.location;
22+
$state = router.stateService;
23+
$transitions = router.transitionService;
24+
router.stateRegistry.stateQueue.autoFlush($state);
25+
26+
var stateTree = {
27+
first: {},
28+
second: {},
29+
third: {},
30+
A: {
31+
url: '/a',
32+
B: {
33+
url: '/b',
34+
C: {
35+
url: '/c',
36+
D: {
37+
url: '/d'
38+
}
39+
}
40+
}
41+
}
42+
};
43+
44+
let states = tree2Array(stateTree, false);
45+
states.forEach(state => router.stateRegistry.register(state));
46+
});
47+
48+
describe('transitionTo', () => {
49+
50+
it("should handle redirects", ((done) => {
51+
$transitions.onStart({ to: 'D'}, trans => (log.push('redirect'), trans.router.stateService.target('C')));
52+
$transitions.onStart({ to: 'C'}, trans => { cOpts = trans.options(); });
53+
54+
var log = [], transition = $state.go("D").transition;
55+
var cOpts: TransitionOptions = {};
56+
57+
wait().then(() => {
58+
expect(log).toEqual(['redirect']);
59+
expect(cOpts.redirectedFrom).toBe(transition);
60+
expect(cOpts.source).toBe("redirect");
61+
})
62+
.then(done, done);
63+
}));
64+
65+
it("should not update the URL in response to synchronizing URL", ((done) => {
66+
$loc.url('/a/b/c');
67+
spyOn($loc, 'url').and.callThrough();
68+
router.urlRouter.sync();
69+
70+
wait().then(() => {
71+
expect($state.current.name).toBe('C');
72+
let pushedUrls = $loc.url.calls.all().map(x => x.args[0]).filter(x => x !== undefined);
73+
expect(pushedUrls).toEqual([]);
74+
expect($loc.path()).toBe('/a/b/c');
75+
done();
76+
})
77+
}));
78+
79+
it("should update the URL in response to synchronizing URL then redirecting", ((done) => {
80+
$transitions.onStart({ to: 'C' }, () => $state.target('D'));
81+
82+
$loc.url('/a/b/c');
83+
spyOn($loc, 'url').and.callThrough();
84+
router.urlRouter.sync();
85+
86+
wait().then(() => {
87+
expect($state.current.name).toBe('D');
88+
let pushedUrls = $loc.url.calls.all().map(x => x.args[0]).filter(x => x !== undefined);
89+
expect(pushedUrls).toEqual(['/a/b/c/d']);
90+
expect($loc.path()).toBe('/a/b/c/d');
91+
done();
92+
})
93+
}));
94+
});
95+
});

0 commit comments

Comments
 (0)