Skip to content

Commit 6743a60

Browse files
feat(Resolve): Switch state.resolve to be an array of Resolvables
This is a prerequisite to supporting ng2 providers BC-BREAK: - Removed the built-in `$resolve$` resolve value, added in a previous alpha BC-BREAK: - `Transition.addResolves()` replaced with `Transition.addResolvable()` BC-BREAK: - The (private API) State object's .resolve property is now pre-processed as an array of Resolvables using statebuilder
1 parent 99e07b2 commit 6743a60

File tree

15 files changed

+106
-71
lines changed

15 files changed

+106
-71
lines changed

src/common/hof.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
* @module common_hof
55
*/
66

7+
import {Predicate} from "./common";
78
/**
89
* Returns a new function for [Partial Application](https://en.wikipedia.org/wiki/Partial_application) of the original function.
910
*
@@ -126,15 +127,15 @@ export const not = (fn) => (...args) => !fn.apply(null, args);
126127
* Given two functions that return truthy or falsey values, returns a function that returns truthy
127128
* if both functions return truthy for the given arguments
128129
*/
129-
export function and(fn1, fn2): Function {
130+
export function and(fn1, fn2): Predicate<any> {
130131
return (...args) => fn1.apply(null, args) && fn2.apply(null, args);
131132
}
132133

133134
/**
134135
* Given two functions that return truthy or falsey values, returns a function that returns truthy
135136
* if at least one of the functions returns truthy for the given arguments
136137
*/
137-
export function or(fn1, fn2): Function {
138+
export function or(fn1, fn2): Predicate<any> {
138139
return (...args) => fn1.apply(null, args) || fn2.apply(null, args);
139140
}
140141

src/common/strings.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,10 @@ export function fnToString(fn: IInjectable) {
6565
return _fn && _fn.toString() || "undefined";
6666
}
6767

68-
const isTransitionRejectionPromise = Rejection.isTransitionRejectionPromise;
69-
7068
let stringifyPatternFn = null;
7169
let stringifyPattern = function(value) {
70+
let isTransitionRejectionPromise = Rejection.isTransitionRejectionPromise;
71+
7272
stringifyPatternFn = stringifyPatternFn || pattern([
7373
[not(isDefined), val("undefined")],
7474
[isNull, val("null")],

src/ng1/legacy/resolveService.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import {State} from "../../state/stateObject";
22
import {PathNode} from "../../path/node";
33
import {ResolveContext} from "../../resolve/resolveContext";
4-
import {Resolvable} from "../../resolve/resolvable";
54
import {map} from "../../common/common";
5+
import {makeResolvables} from "../statebuilders/resolve";
66

77
export const resolveFactory = () => ({
88
/**
@@ -12,14 +12,14 @@ export const resolveFactory = () => ({
1212
* @param parent a promise for a "parent resolve"
1313
*/
1414
resolve: (invocables, locals = {}, parent?) => {
15-
let parentNode = new PathNode(new State(<any> { params: {} }));
16-
let node = new PathNode(new State(<any> { params: {} }));
15+
let parentNode = new PathNode(new State(<any> { params: {}, resolve: [] }));
16+
let node = new PathNode(new State(<any> { params: {}, resolve: [] }));
1717
let context = new ResolveContext([parentNode, node]);
1818

19-
context.addResolvables(Resolvable.makeResolvables(invocables), node.state);
19+
context.addResolvables(makeResolvables(invocables), node.state);
2020

2121
const resolveData = (parentLocals) => {
22-
const rewrap = _locals => Resolvable.makeResolvables(<any> map(_locals, local => () => local));
22+
const rewrap = _locals => makeResolvables(<any> map(_locals, local => () => local));
2323
context.addResolvables(rewrap(parentLocals), parentNode.state);
2424
context.addResolvables(rewrap(locals), node.state);
2525
return context.resolvePath();

src/ng1/statebuilders/resolve.ts

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
/** @module ng1 */ /** */
22
import {State} from "../../state/stateObject";
3-
import {forEach} from "../../common/common";
4-
import {isString} from "../../common/predicates";
3+
import {isObject, isString, isInjectable} from "../../common/predicates";
4+
import {Resolvable} from "../../resolve/resolvable";
5+
import {services} from "../../common/coreservices";
56

67
/**
78
* This is a [[StateBuilder.builder]] function for angular1 `resolve:` block on a [[Ng1StateDeclaration]].
@@ -10,9 +11,29 @@ import {isString} from "../../common/predicates";
1011
* handles the `resolve` property with logic specific to angular-ui-router (ng1).
1112
*/
1213
export function ng1ResolveBuilder(state: State) {
13-
let resolve = {};
14-
forEach(state.resolve || {}, function (resolveFn, name: string) {
15-
resolve[name] = isString(resolveFn) ? [ resolveFn, x => x ] : resolveFn;
16-
});
17-
return resolve;
14+
return isObject(state.resolve) ? makeResolvables(state.resolve) : [];
15+
}
16+
17+
/** Validates the result map as a "resolve:" style object, then transforms the resolves into Resolvable[] */
18+
export function makeResolvables(resolves: { [key: string]: Function; }): Resolvable[] {
19+
// desugar ng1 sugar to create a resolve that is a service
20+
// e.g., resolve: { myService: 'myService' }
21+
const resolveServiceFromString = tuple => {
22+
if (!isString(tuple.val)) return tuple;
23+
24+
injectService.$inject = [tuple.val];
25+
function injectService(svc) { return svc; }
26+
return { key: tuple.key, val: injectService };
27+
};
28+
29+
// Convert from object to tuple array
30+
let tuples = Object.keys(resolves).map(key => ({key, val: resolves[key]})).map(resolveServiceFromString);
31+
32+
// If a hook result is an object, it should be a map of strings to (functions|strings).
33+
let invalid = tuples.filter(tuple => !isInjectable(tuple.val));
34+
if (invalid.length)
35+
throw new Error(`Invalid resolve key/value: ${invalid[0].key}/${invalid[0].val}`);
36+
37+
const deps = (resolveFn) => services.$injector.annotate(resolveFn, services.$injector.strictDi);
38+
return tuples.map(tuple => new Resolvable(tuple.key, tuple.val, deps(tuple.val)));
1839
}

src/path/node.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export class PathNode {
4646
this.state = state;
4747
this.paramSchema = state.parameters({ inherit: false });
4848
this.paramValues = {};
49-
this.resolvables = Object.keys(state.resolve || {}).map(key => new Resolvable(key, state.resolve[key]));
49+
this.resolvables = state.resolve.map(res => res.clone());
5050
}
5151
}
5252

src/resolve/resolvable.ts

Lines changed: 35 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
/** @module resolve */ /** for typedoc */
2-
import {extend, pick, map, filter} from "../common/common";
3-
import {not} from "../common/hof";
4-
import {isInjectable} from "../common/predicates";
2+
import {pick, map, extend} from "../common/common";
53

64
import {services} from "../common/coreservices";
75
import {trace} from "../common/trace";
86
import {Resolvables, IOptions1} from "./interface";
97

108
import {ResolveContext} from "./resolveContext";
9+
import {stringify} from "../common/strings";
1110

1211
/**
1312
* The basic building block for the resolve system.
@@ -22,18 +21,39 @@ import {ResolveContext} from "./resolveContext";
2221
* parameter to those fns.
2322
*/
2423
export class Resolvable {
25-
name: string;
24+
token: any;
2625
resolveFn: Function;
2726
deps: string[];
2827

2928
promise: Promise<any> = undefined;
29+
resolved: boolean = false;
3030
data: any;
31-
32-
constructor(name: string, resolveFn: Function, preResolvedData?: any) {
33-
this.name = name;
34-
this.resolveFn = resolveFn;
35-
this.deps = services.$injector.annotate(resolveFn, services.$injector.strictDi);
36-
this.data = preResolvedData;
31+
32+
/**
33+
* This constructor creates a Resolvable copy
34+
*/
35+
constructor(resolvable: Resolvable)
36+
37+
/**
38+
* This constructor creates a new `Resolvable`
39+
*
40+
* @param token The new resolvable's injection token, such as `"userList"` (a string) or `UserService` (a class).
41+
* When this token is used during injection, the resolved value will be injected.
42+
* @param resolveFn The function that returns the resolved value, or a promise for the resolved value
43+
* @param deps An array of dependencies, which will be injected into the `resolveFn`
44+
* @param data Pre-resolved data. If the resolve value is already known, it may be provided here.
45+
*/
46+
constructor(token: any, resolveFn: Function, deps?: any[], data?: any)
47+
constructor(token, resolveFn?: Function, deps?: any[], data?: any) {
48+
if (token instanceof Resolvable) {
49+
extend(this, token);
50+
} else {
51+
this.token = token;
52+
this.resolveFn = resolveFn;
53+
this.deps = deps;
54+
this.data = data;
55+
this.resolved = data !== undefined;
56+
}
3757
}
3858

3959
// synchronous part:
@@ -48,15 +68,15 @@ export class Resolvable {
4868
// - store unwrapped data
4969
// - resolve the Resolvable's promise
5070
resolveResolvable(resolveContext: ResolveContext, options: IOptions1 = {}) {
51-
let {name, deps, resolveFn} = this;
71+
let {deps, resolveFn} = this;
5272

5373
trace.traceResolveResolvable(this, options);
5474
// First, set up an overall deferred/promise for this Resolvable
5575
let deferred = services.$q.defer();
5676
this.promise = deferred.promise;
5777
// Load a map of all resolvables for this state from the context path
5878
// Omit the current Resolvable from the result, so we don't try to inject this into this
59-
let ancestorsByName: Resolvables = resolveContext.getResolvables(null, { omitOwnLocals: [ name ] });
79+
let ancestorsByName: Resolvables = resolveContext.getResolvables(null, { omitOwnLocals: [ this.token ] });
6080

6181
// Limit the ancestors Resolvables map to only those that the current Resolvable fn's annotations depends on
6282
let depResolvables: Resolvables = <any> pick(ancestorsByName, deps);
@@ -86,17 +106,10 @@ export class Resolvable {
86106
}
87107

88108
toString() {
89-
return `Resolvable(name: ${this.name}, requires: [${this.deps}])`;
109+
return `Resolvable(token: ${stringify(this.token)}, requires: [${this.deps.map(stringify)}])`;
90110
}
91111

92-
/**
93-
* Validates the result map as a "resolve:" style object, then transforms the resolves into Resolvable[]
94-
*/
95-
static makeResolvables(resolves: { [key: string]: Function; }): Resolvable[] {
96-
// If a hook result is an object, it should be a map of strings to functions.
97-
let invalid = filter(resolves, not(isInjectable)), keys = Object.keys(invalid);
98-
if (keys.length)
99-
throw new Error(`Invalid resolve key/value: ${keys[0]}/${invalid[keys[0]]}`);
100-
return Object.keys(resolves).map(key => new Resolvable(key, resolves[key]));
112+
clone(): Resolvable {
113+
return new Resolvable(this);
101114
}
102115
}

src/resolve/resolveContext.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,8 @@ export class ResolveContext {
6666
let omitProps = (node === last) ? options.omitOwnLocals : [];
6767

6868
let filteredResolvables = node.resolvables
69-
.filter(r => omitProps.indexOf(r.name) === -1)
70-
.reduce((acc, r) => { acc[r.name] = r; return acc; }, {});
69+
.filter(r => omitProps.indexOf(r.token) === -1)
70+
.reduce((acc, r) => { acc[r.token] = r; return acc; }, {});
7171

7272
return extend(memo, filteredResolvables);
7373
}, <Resolvables> {});
@@ -85,14 +85,14 @@ export class ResolveContext {
8585

8686
addResolvables(newResolvables: Resolvable[], state: State) {
8787
var node = this._nodeFor(state);
88-
var keys = newResolvables.map(r => r.name);
89-
node.resolvables = node.resolvables.filter(r => keys.indexOf(r.name) === -1).concat(newResolvables);
88+
var keys = newResolvables.map(r => r.token);
89+
node.resolvables = node.resolvables.filter(r => keys.indexOf(r.token) === -1).concat(newResolvables);
9090
}
9191

9292
/** Gets the resolvables declared on a particular state */
9393
getOwnResolvables(state: State): Resolvables {
9494
return this._nodeFor(state).resolvables
95-
.reduce((acc, r) => { acc[r.name] = r; return acc; }, <Resolvables>{});
95+
.reduce((acc, r) => { acc[r.token] = r; return acc; }, <Resolvables>{});
9696
}
9797

9898
// Returns a promise for an array of resolved path Element promises
@@ -200,6 +200,6 @@ function getPolicy(stateResolvePolicyConf, resolvable: Resolvable): number {
200200
// Normalize the configuration on the state to either state-level (a string) or resolve-level (a Map of string:string)
201201
let stateLevelPolicy: string = <string> (isString(stateResolvePolicyConf) ? stateResolvePolicyConf : null);
202202
let resolveLevelPolicies: IPolicies = <any> (isObject(stateResolvePolicyConf) ? stateResolvePolicyConf : {});
203-
let policyName = resolveLevelPolicies[resolvable.name] || stateLevelPolicy || defaultResolvePolicy;
203+
let policyName = resolveLevelPolicies[resolvable.token] || stateLevelPolicy || defaultResolvePolicy;
204204
return ResolvePolicy[policyName];
205205
}

src/state/hooks/resolveHooks.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,10 @@ export class ResolveHooks {
3636

3737
// A new Resolvable contains all the resolved data in this context as a single object, for injection as `$resolve$`
3838
let context = node.resolveContext;
39-
let $resolve$ = new Resolvable("$resolve$", () => map(context.getResolvables(), (r: Resolvable) => r.data));
4039
var options = extend({ transition: transition }, { resolvePolicy: LAZY });
4140

42-
// Resolve all the LAZY resolves, then resolve the `$resolve$` object, then add `$resolve$` to the context
43-
// return context.resolvePathElement(node.state, options)
44-
return context.resolvePath(options)
45-
.then(() => $resolve$.resolveResolvable(context))
46-
.then(() => context.addResolvables([$resolve$], node.state));
41+
// Resolve all the LAZY resolves
42+
return context.resolvePath(options);
4743
}
4844

4945
// Resolve eager resolvables before when the transition starts

src/state/stateQueueManager.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export class StateQueueManager {
2323
// @TODO: state = new State(extend({}, config, { ... }))
2424
let state = inherit(new State(), extend({}, config, {
2525
self: config,
26-
resolve: config.resolve || {},
26+
resolve: config.resolve || [],
2727
toString: () => config.name
2828
}));
2929

src/transition/transition.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -154,8 +154,8 @@ export class Transition implements IHookRegistry {
154154
PathFactory.bindResolveContexts(this._treeChanges.to);
155155

156156
let rootResolvables: Resolvable[] = [
157-
new Resolvable('$transition$', () => this, this),
158-
new Resolvable('$stateParams', () => this.params(), this.params())
157+
new Resolvable('$transition$', () => this, [], this),
158+
new Resolvable('$stateParams', () => this.params(), [], this.params())
159159
];
160160
let rootNode: PathNode = this._treeChanges.to[0];
161161
rootNode.resolveContext.addResolvables(rootResolvables, rootNode.state)
@@ -223,16 +223,16 @@ export class Transition implements IHookRegistry {
223223
}
224224

225225
/**
226-
* Adds new resolves to this transition.
226+
* Adds a new [[Resolvable]] (`resolve`) to this transition.
227227
*
228-
* @param resolves an [[ResolveDeclarations]] object which describes the new resolves
229-
* @param state the state in the "to path" which should receive the new resolves (otherwise, the root state)
228+
* @param resolvable an [[Resolvable]] object
229+
* @param state the state in the "to path" which should receive the new resolve (otherwise, the root state)
230230
*/
231-
addResolves(resolves: { [key: string]: Function }, state: StateOrName = ""): void {
231+
addResolvable(resolvable: Resolvable, state: StateOrName = ""): void {
232232
let stateName: string = (typeof state === "string") ? state : state.name;
233233
let topath = this._treeChanges.to;
234234
let targetNode = find(topath, node => node.state.name === stateName);
235-
tail(topath).resolveContext.addResolvables(Resolvable.makeResolvables(resolves), targetNode.state);
235+
tail(topath).resolveContext.addResolvables([resolvable], targetNode.state);
236236
}
237237

238238
/**

0 commit comments

Comments
 (0)