Skip to content

Commit 3a5d055

Browse files
feat(State): Support registration of ES6 state classes (as opposed to object literals)
1 parent cef2a73 commit 3a5d055

10 files changed

+70
-35
lines changed

src/state/interface.ts

+36-7
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ export type ResolveTypes = Resolvable | ResolvableLiteral | ProviderLike;
3030
* Base interface for declaring a view
3131
*
3232
* This interface defines the basic data that a normalized view declaration will have on it.
33-
* Framework-specific implementations should add additional fields (to their interfaces which extend this interface).
33+
* Each implementation of UI-Router (for a specific framework) should define its own extension of this interface.
34+
* Add any additional fields that the framework requires to that interface.
3435
*
3536
* @internalapi
3637
*/
@@ -90,25 +91,48 @@ export type RedirectToResult = string | TargetState | { state?: string, params?:
9091
/**
9192
* The StateDeclaration object is used to define a state or nested state.
9293
*
94+
* Note: Each implementation of UI-Router (for a specific framework)
95+
* extends this interface as necessary.
96+
*
9397
* #### Example:
9498
* ```js
9599
* // StateDeclaration object
96100
* var foldersState = {
97101
* name: 'folders',
98102
* url: '/folders',
103+
* component: FoldersComponent,
99104
* resolve: {
100105
* allfolders: function(FolderService) {
101106
* return FolderService.list();
102107
* }
103108
* },
104-
* template: "<ul><li ng-repeat='folder in allfolders'>{{folder.name}}</li></ul>",
105-
* controller: function(allfolders, $scope) {
106-
* $scope.allfolders = allfolders;
107-
* }
108109
* }
110+
*
111+
* registry.register(foldersState);
109112
* ```
110113
*
111-
* Note: Each front-end framework extends this interface as necessary
114+
* A state declaration may also be an ES6 class which implements the StateDeclaration interface
115+
* and has a `@State()` decorator
116+
*
117+
* #### Example:
118+
* ```js
119+
* import { State } from "ui-router-core"
120+
* import { FolderService } from "../folder.service"
121+
* // StateDeclaration class
122+
* @State()
123+
* export class FoldersState implements StateDeclaration {
124+
* name: 'folders',
125+
* url: '/folders',
126+
* component: FoldersComponent
127+
*
128+
* @Resolve({ deps: [ FolderService ] })
129+
* allfolders(FolderService) {
130+
* return FolderService.list();
131+
* },
132+
* }
133+
*
134+
* registry.register(FoldersState);
135+
* ```
112136
*/
113137
export interface StateDeclaration {
114138
/**
@@ -161,7 +185,7 @@ export interface StateDeclaration {
161185
*
162186
* Gets the *internal API* for a registered state.
163187
*
164-
* Note: the internal [[State]] API is subject to change without notice
188+
* Note: the internal [[StateObject]] API is subject to change without notice
165189
*/
166190
$$state?: () => StateObject;
167191

@@ -685,3 +709,8 @@ export interface HrefOptions {
685709
absolute?: boolean;
686710
}
687711

712+
/**
713+
* Either a [[StateDeclaration]] or an ES6 class that implements [[StateDeclaration]]
714+
* The ES6 class constructor should have no arguments.
715+
*/
716+
export type _StateDeclaration = StateDeclaration | { new (): StateDeclaration };

src/state/stateBuilder.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ function includesBuilder(state: StateObject) {
105105
/**
106106
* This is a [[StateBuilder.builder]] function for the `resolve:` block on a [[StateDeclaration]].
107107
*
108-
* When the [[StateBuilder]] builds a [[State]] object from a raw [[StateDeclaration]], this builder
108+
* When the [[StateBuilder]] builds a [[StateObject]] object from a raw [[StateDeclaration]], this builder
109109
* validates the `resolve` property and converts it to a [[Resolvable]] array.
110110
*
111111
* resolve: input value can be:
@@ -204,13 +204,13 @@ export function resolvablesBuilder(state: StateObject): Resolvable[] {
204204
/**
205205
* @internalapi A internal global service
206206
*
207-
* StateBuilder is a factory for the internal [[State]] objects.
207+
* StateBuilder is a factory for the internal [[StateObject]] objects.
208208
*
209209
* When you register a state with the [[StateRegistry]], you register a plain old javascript object which
210210
* conforms to the [[StateDeclaration]] interface. This factory takes that object and builds the corresponding
211-
* [[State]] object, which has an API and is used internally.
211+
* [[StateObject]] object, which has an API and is used internally.
212212
*
213-
* Custom properties or API may be added to the internal [[State]] object by registering a decorator function
213+
* Custom properties or API may be added to the internal [[StateObject]] object by registering a decorator function
214214
* using the [[builder]] method.
215215
*/
216216
export class StateBuilder {
@@ -250,10 +250,10 @@ export class StateBuilder {
250250
}
251251

252252
/**
253-
* Registers a [[BuilderFunction]] for a specific [[State]] property (e.g., `parent`, `url`, or `path`).
253+
* Registers a [[BuilderFunction]] for a specific [[StateObject]] property (e.g., `parent`, `url`, or `path`).
254254
* More than one BuilderFunction can be registered for a given property.
255255
*
256-
* The BuilderFunction(s) will be used to define the property on any subsequently built [[State]] objects.
256+
* The BuilderFunction(s) will be used to define the property on any subsequently built [[StateObject]] objects.
257257
*
258258
* @param name The name of the State property being registered for.
259259
* @param fn The BuilderFunction which will be used to build the State property

src/state/stateObject.ts

+15-9
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* @module state
44
*/
55
/** for typedoc */
6-
import { StateDeclaration, _ViewDeclaration } from "./interface";
6+
import { StateDeclaration, _ViewDeclaration, _StateDeclaration } from "./interface";
77
import { defaults, values, find, inherit } from "../common/common";
88
import { propEq } from "../common/hof";
99
import { Param } from "../params/param";
@@ -13,20 +13,20 @@ import { TransitionStateHookFn } from "../transition/interface";
1313
import { TargetState } from "./targetState";
1414
import { Transition } from "../transition/transition";
1515
import { Glob } from "../common/glob";
16-
import { isObject } from "../common/predicates";
16+
import { isObject, isFunction } from "../common/predicates";
1717

1818
/**
1919
* Internal representation of a UI-Router state.
2020
*
2121
* Instances of this class are created when a [[StateDeclaration]] is registered with the [[StateRegistry]].
2222
*
23-
* A registered [[StateDeclaration]] is augmented with a getter ([[StateDeclaration.$$state]]) which returns the corresponding [[State]] object.
23+
* A registered [[StateDeclaration]] is augmented with a getter ([[StateDeclaration.$$state]]) which returns the corresponding [[StateObject]] object.
2424
*
2525
* This class prototypally inherits from the corresponding [[StateDeclaration]].
2626
* Each of its own properties (i.e., `hasOwnProperty`) are built using builders from the [[StateBuilder]].
2727
*/
2828
export class StateObject {
29-
/** The parent [[State]] */
29+
/** The parent [[StateObject]] */
3030
public parent: StateObject;
3131

3232
/** The name used to register the state */
@@ -58,15 +58,15 @@ export class StateObject {
5858
public views: { [key: string]: _ViewDeclaration; };
5959

6060
/**
61-
* The original [[StateDeclaration]] used to build this [[State]].
61+
* The original [[StateDeclaration]] used to build this [[StateObject]].
6262
* Note: `this` object also prototypally inherits from the `self` declaration object.
6363
*/
6464
public self: StateDeclaration;
6565

66-
/** The nearest parent [[State]] which has a URL */
66+
/** The nearest parent [[StateObject]] which has a URL */
6767
public navigable: StateObject;
6868

69-
/** The parent [[State]] objects from this state up to the root */
69+
/** The parent [[StateObject]] objects from this state up to the root */
7070
public path: StateObject[];
7171

7272
/**
@@ -117,7 +117,9 @@ export class StateObject {
117117
* @param stateDecl the user-supplied State Declaration
118118
* @returns {StateObject} an internal State object
119119
*/
120-
static create(stateDecl: StateDeclaration): StateObject {
120+
static create(stateDecl: _StateDeclaration): StateObject {
121+
stateDecl = StateObject.isStateClass(stateDecl) ? new stateDecl() : stateDecl;
122+
121123
let state = inherit(inherit(stateDecl, StateObject.prototype)) as StateObject;
122124
stateDecl.$$state = () => state;
123125
state.self = stateDecl;
@@ -127,7 +129,11 @@ export class StateObject {
127129
return state;
128130
}
129131

130-
/** Predicate which returns true if the object is an internal [[State]] object */
132+
/** Predicate which returns true if the object is an class with @State() decorator */
133+
static isStateClass = (stateDecl: _StateDeclaration): stateDecl is ({ new (): StateDeclaration }) =>
134+
isFunction(stateDecl) && stateDecl['__uiRouterState'] === true;
135+
136+
/** Predicate which returns true if the object is an internal [[StateObject]] object */
131137
static isState = (obj: any): obj is StateObject =>
132138
isObject(obj['__stateObjectCache']);
133139

src/state/stateQueueManager.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/** @module state */ /** for typedoc */
22
import { inArray } from "../common/common";
33
import { isString } from "../common/predicates";
4-
import { StateDeclaration } from "./interface";
4+
import { StateDeclaration, _StateDeclaration } from "./interface";
55
import { StateObject } from "./stateObject";
66
import { StateBuilder } from "./stateBuilder";
77
import { StateRegistryListener, StateRegistry } from "./stateRegistry";
@@ -30,7 +30,7 @@ export class StateQueueManager implements Disposable {
3030
this.queue = [];
3131
}
3232

33-
register(stateDecl: StateDeclaration) {
33+
register(stateDecl: _StateDeclaration) {
3434
let queue = this.queue;
3535
let state = StateObject.create(stateDecl);
3636
let name = state.name;

src/state/stateRegistry.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { StateObject } from "./stateObject";
77
import { StateMatcher } from "./stateMatcher";
88
import { StateBuilder } from "./stateBuilder";
99
import { StateQueueManager } from "./stateQueueManager";
10-
import { StateDeclaration } from "./interface";
10+
import { StateDeclaration, _StateDeclaration } from "./interface";
1111
import { BuilderFunction } from "./stateBuilder";
1212
import { StateOrName } from "./interface";
1313
import { removeFrom } from "../common/common";
@@ -107,9 +107,9 @@ export class StateRegistry {
107107
*
108108
* Gets the root of the state tree.
109109
* The root state is implicitly created by UI-Router.
110-
* Note: this returns the internal [[State]] representation, not a [[StateDeclaration]]
110+
* Note: this returns the internal [[StateObject]] representation, not a [[StateDeclaration]]
111111
*
112-
* @return the root [[State]]
112+
* @return the root [[StateObject]]
113113
*/
114114
root() {
115115
return this._root;
@@ -123,11 +123,11 @@ export class StateRegistry {
123123
* Note: a state will be queued if the state's parent isn't yet registered.
124124
*
125125
* @param stateDefinition the definition of the state to register.
126-
* @returns the internal [[State]] object.
126+
* @returns the internal [[StateObject]] object.
127127
* If the state was successfully registered, then the object is fully built (See: [[StateBuilder]]).
128128
* If the state was only queued, then the object is not fully built.
129129
*/
130-
register(stateDefinition: StateDeclaration): StateObject {
130+
register(stateDefinition: _StateDeclaration): StateObject {
131131
return this.stateQueue.register(stateDefinition);
132132
}
133133

src/state/stateService.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ export class StateService {
6565
*/
6666
get current() { return this.router.globals.current; }
6767
/**
68-
* The current [[State]]
68+
* The current [[StateObject]]
6969
*
7070
* This is a passthrough through to [[UIRouterGlobals.$current]]
7171
*/

src/state/targetState.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ import { isString } from "../common/predicates";
3333
* 4) the registered state object (the [[StateDeclaration]])
3434
*
3535
* Many UI-Router APIs such as [[StateService.go]] take a [[StateOrName]] argument which can
36-
* either be a *state object* (a [[StateDeclaration]] or [[State]]) or a *state name* (a string).
36+
* either be a *state object* (a [[StateDeclaration]] or [[StateObject]]) or a *state name* (a string).
3737
* The `TargetState` class normalizes those options.
3838
*
3939
* A `TargetState` may be valid (the state being targeted exists in the registry)

src/transition/interface.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ export interface TransitionHookFn {
198198
* As each lifecycle event occurs, the hooks which are registered for the event and that state are called (in priority order).
199199
*
200200
* @param transition the current [[Transition]]
201-
* @param state the [[State]] that the hook is bound to
201+
* @param state the [[StateObject]] that the hook is bound to
202202
* @param injector (for ng1 or ng2 only) the injector service
203203
*
204204
* @returns a [[HookResult]] which may alter the transition
@@ -700,7 +700,7 @@ export interface IHookRegistry {
700700
_registeredHooks: { [key: string]: RegisteredHook[] }
701701
}
702702

703-
/** A predicate type which takes a [[State]] and returns a boolean */
703+
/** A predicate type which takes a [[StateObject]] and returns a boolean */
704704
export type IStateMatch = Predicate<StateObject>
705705
/**
706706
* This object is used to configure whether or not a Transition Hook is invoked for a particular transition,

src/url/urlRule.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import {
2121
* - `string`
2222
* - [[UrlMatcher]]
2323
* - `RegExp`
24-
* - [[State]]
24+
* - [[StateObject]]
2525
* @internalapi
2626
*/
2727
export class UrlRuleFactory {

src/view/interface.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export interface ActiveUIView {
3333
*
3434
* A `ViewConfig` is the runtime definition of a single view.
3535
*
36-
* During a transition, `ViewConfig`s are created for each [[_ViewDeclaration]] defined on each "entering" [[State]].
36+
* During a transition, `ViewConfig`s are created for each [[_ViewDeclaration]] defined on each "entering" [[StateObject]].
3737
* Then, the [[ViewService]] finds any matching `ui-view`(s) in the DOM, and supplies the ui-view
3838
* with the `ViewConfig`. The `ui-view` then loads itself using the information found in the `ViewConfig`.
3939
*

0 commit comments

Comments
 (0)