Skip to content

Commit 027c995

Browse files
feat(State): Switch Internal State Object to prototypally inherit from the State Declaration
In 0.x, we used prototypal inheritance, i.e. `internalImpl = inherit(stateDeclaration)`. In 1.0 alphas, we switched to `extend(new State(), stateDeclaration)` to enable us to use `instanceof State`. However, this convenience for ui-router code was inconvenient for end users so we're switching back to using the state declaration as the prototype of the internal state object. - This enables users to create classes for their states, providing (e.g.) standardized onEnter functions, such as: ```js var myState = new LoggingState('mystate', '/url'); ``` Closes angular-ui/ui-router#3293 Closes #34
1 parent ef6c87b commit 027c995

File tree

6 files changed

+200
-87
lines changed

6 files changed

+200
-87
lines changed

src/common/predicates.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@
44
* Although these functions are exported, they are subject to change without notice.
55
*
66
* @module common_predicates
7-
*/ /** */
8-
import { and, not, pipe, prop, compose, or } from "./hof";
9-
import {Predicate} from "./common"; // has or is using
7+
*/
8+
/** */
9+
import { and, not, pipe, prop, or } from "./hof";
10+
import { Predicate } from "./common"; // has or is using
11+
import { State } from "../state/stateObject";
1012

1113
const toStr = Object.prototype.toString;
1214
const tis = (t: string) => (x: any) => typeof(x) === t;
@@ -21,6 +23,7 @@ export const isObject = (x: any) => x !== null && typeof x === 'object';
2123
export const isArray = Array.isArray;
2224
export const isDate: (x: any) => x is Date = <any> ((x: any) => toStr.call(x) === '[object Date]');
2325
export const isRegExp: (x: any) => x is RegExp = <any> ((x: any) => toStr.call(x) === '[object RegExp]');
26+
export const isState: (x: any) => x is State = State.isState;
2427

2528
/**
2629
* Predicate which checks if a value is injectable

src/router.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,13 @@ export class UIRouter {
5252

5353
/**
5454
* Deprecated for public use. Use [[urlService]] instead.
55-
* @deprecated
55+
* @deprecated Use [[urlService]] instead
5656
*/
5757
urlMatcherFactory: UrlMatcherFactory = new UrlMatcherFactory();
5858

5959
/**
6060
* Deprecated for public use. Use [[urlService]] instead.
61-
* @deprecated
61+
* @deprecated Use [[urlService]] instead
6262
*/
6363
urlRouter: UrlRouter = new UrlRouter(this);
6464

src/state/stateObject.ts

+33-13
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
/**
22
* @coreapi
33
* @module state
4-
*/ /** for typedoc */
5-
6-
import {StateDeclaration, _ViewDeclaration} from "./interface";
7-
import {extend, defaults, values, find} from "../common/common";
8-
import {propEq} from "../common/hof";
9-
import {Param} from "../params/param";
10-
import {UrlMatcher} from "../url/urlMatcher";
11-
import {Resolvable} from "../resolve/resolvable";
12-
import {TransitionStateHookFn} from "../transition/interface";
13-
import {TargetState} from "./targetState";
14-
import {Transition} from "../transition/transition";
4+
*/
5+
/** for typedoc */
6+
import { StateDeclaration, _ViewDeclaration } from "./interface";
7+
import { defaults, values, find, inherit } from "../common/common";
8+
import { propEq } from "../common/hof";
9+
import { Param } from "../params/param";
10+
import { UrlMatcher } from "../url/urlMatcher";
11+
import { Resolvable } from "../resolve/resolvable";
12+
import { TransitionStateHookFn } from "../transition/interface";
13+
import { TargetState } from "./targetState";
14+
import { Transition } from "../transition/transition";
1515

1616
/**
1717
* Internal representation of a UI-Router state.
@@ -96,11 +96,31 @@ export class State {
9696
);
9797

9898

99+
/** @deprecated use State.create() */
99100
constructor(config?: StateDeclaration) {
100-
extend(this, config);
101-
// Object.freeze(this);
101+
return State.create(config || {});
102102
}
103103

104+
/**
105+
* Create a state object to put the private/internal implementation details onto.
106+
* The object's prototype chain looks like:
107+
* (Internal State Object) -> (Copy of State.prototype) -> (State Declaration object) -> (State Declaration's prototype...)
108+
*
109+
* @param stateDecl the user-supplied State Declaration
110+
* @returns {State} an internal State object
111+
*/
112+
static create(stateDecl: StateDeclaration): State {
113+
let state = inherit(inherit(stateDecl, State.prototype)) as State;
114+
stateDecl.$$state = () => state;
115+
state['__stateObject'] = true;
116+
state.self = stateDecl;
117+
return state;
118+
}
119+
120+
/** Predicate which returns true if the object is an internal State object */
121+
static isState = (obj: any): obj is State =>
122+
obj['__stateObject'] === true;
123+
104124
/**
105125
* Returns true if the provided parameter is the same state.
106126
*

src/state/stateQueueManager.ts

+11-15
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
/** @module state */ /** for typedoc */
2-
import { extend, inherit, pluck } from "../common/common";
3-
import { isString } from "../common/predicates";
2+
import { extend, inherit, pluck, inArray } from "../common/common";
3+
import { isString, isDefined } from "../common/predicates";
44
import { StateDeclaration } from "./interface";
55
import { State } from "./stateObject";
66
import { StateBuilder } from "./stateBuilder";
77
import { StateRegistryListener, StateRegistry } from "./stateRegistry";
88
import { Disposable } from "../interface";
99
import { UrlRouter } from "../url/urlRouter";
10+
import { prop } from "../common/hof";
1011

1112
/** @internalapi */
1213
export class StateQueueManager implements Disposable {
@@ -26,19 +27,14 @@ export class StateQueueManager implements Disposable {
2627
this.queue = [];
2728
}
2829

29-
register(config: StateDeclaration) {
30-
let {states, queue} = this;
31-
// Wrap a new object around the state so we can store our private details easily.
32-
// @TODO: state = new State(extend({}, config, { ... }))
33-
let state = inherit(new State(), extend({}, config, {
34-
self: config,
35-
resolve: config.resolve || [],
36-
toString: () => config.name
37-
}));
38-
39-
if (!isString(state.name)) throw new Error("State must have a valid name");
40-
if (states.hasOwnProperty(state.name) || pluck(queue, 'name').indexOf(state.name) !== -1)
41-
throw new Error(`State '${state.name}' is already defined`);
30+
register(stateDecl: StateDeclaration) {
31+
let queue = this.queue;
32+
let state = State.create(stateDecl);
33+
let name = state.name;
34+
35+
if (!isString(name)) throw new Error("State must have a valid name");
36+
if (this.states.hasOwnProperty(name) || inArray(queue.map(prop('name')), name))
37+
throw new Error(`State '${name}' is already defined`);
4238

4339
queue.push(state);
4440
this.flush();

src/url/urlRule.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* @module url
44
*/ /** */
55
import { UrlMatcher } from "./urlMatcher";
6-
import { isString, isDefined, isFunction } from "../common/predicates";
6+
import { isString, isDefined, isFunction, isState } from "../common/predicates";
77
import { UIRouter } from "../router";
88
import { identity, extend } from "../common/common";
99
import { is, pattern } from "../common/hof";
@@ -38,7 +38,7 @@ export class UrlRuleFactory {
3838
const makeRule = pattern([
3939
[isString, (_what: string) => makeRule(this.compile(_what))],
4040
[is(UrlMatcher), (_what: UrlMatcher) => this.fromUrlMatcher(_what, handler)],
41-
[is(State), (_what: State) => this.fromState(_what, this.router)],
41+
[isState, (_what: State) => this.fromState(_what, this.router)],
4242
[is(RegExp), (_what: RegExp) => this.fromRegExp(_what, handler)],
4343
[isFunction, (_what: UrlRuleMatchFn) => new BaseUrlRule(_what, handler as UrlRuleHandlerFn)],
4444
]);

0 commit comments

Comments
 (0)