diff --git a/src/state/interface.ts b/src/state/interface.ts
index 34c1e56b..a2aee26b 100644
--- a/src/state/interface.ts
+++ b/src/state/interface.ts
@@ -30,7 +30,8 @@ export type ResolveTypes = Resolvable | ResolvableLiteral | ProviderLike;
* Base interface for declaring a view
*
* This interface defines the basic data that a normalized view declaration will have on it.
- * Framework-specific implementations should add additional fields (to their interfaces which extend this interface).
+ * Each implementation of UI-Router (for a specific framework) should define its own extension of this interface.
+ * Add any additional fields that the framework requires to that interface.
*
* @internalapi
*/
@@ -90,25 +91,48 @@ export type RedirectToResult = string | TargetState | { state?: string, params?:
/**
* The StateDeclaration object is used to define a state or nested state.
*
+ * Note: Each implementation of UI-Router (for a specific framework)
+ * extends this interface as necessary.
+ *
* #### Example:
* ```js
* // StateDeclaration object
* var foldersState = {
* name: 'folders',
* url: '/folders',
+ * component: FoldersComponent,
* resolve: {
* allfolders: function(FolderService) {
* return FolderService.list();
* }
* },
- * template: "
",
- * controller: function(allfolders, $scope) {
- * $scope.allfolders = allfolders;
- * }
* }
+ *
+ * registry.register(foldersState);
* ```
*
- * Note: Each front-end framework extends this interface as necessary
+ * A state declaration may also be an ES6 class which implements the StateDeclaration interface
+ * and has a `@State()` decorator
+ *
+ * #### Example:
+ * ```js
+ * import { State } from "ui-router-core"
+ * import { FolderService } from "../folder.service"
+ * // StateDeclaration class
+ * @State()
+ * export class FoldersState implements StateDeclaration {
+ * name: 'folders',
+ * url: '/folders',
+ * component: FoldersComponent
+ *
+ * @Resolve({ deps: [ FolderService ] })
+ * allfolders(FolderService) {
+ * return FolderService.list();
+ * },
+ * }
+ *
+ * registry.register(FoldersState);
+ * ```
*/
export interface StateDeclaration {
/**
@@ -161,7 +185,7 @@ export interface StateDeclaration {
*
* Gets the *internal API* for a registered state.
*
- * Note: the internal [[State]] API is subject to change without notice
+ * Note: the internal [[StateObject]] API is subject to change without notice
*/
$$state?: () => StateObject;
@@ -685,3 +709,8 @@ export interface HrefOptions {
absolute?: boolean;
}
+/**
+ * Either a [[StateDeclaration]] or an ES6 class that implements [[StateDeclaration]]
+ * The ES6 class constructor should have no arguments.
+ */
+export type _StateDeclaration = StateDeclaration | { new (): StateDeclaration };
diff --git a/src/state/stateBuilder.ts b/src/state/stateBuilder.ts
index 4bf94c6e..e38cf20d 100644
--- a/src/state/stateBuilder.ts
+++ b/src/state/stateBuilder.ts
@@ -105,7 +105,7 @@ function includesBuilder(state: StateObject) {
/**
* This is a [[StateBuilder.builder]] function for the `resolve:` block on a [[StateDeclaration]].
*
- * When the [[StateBuilder]] builds a [[State]] object from a raw [[StateDeclaration]], this builder
+ * When the [[StateBuilder]] builds a [[StateObject]] object from a raw [[StateDeclaration]], this builder
* validates the `resolve` property and converts it to a [[Resolvable]] array.
*
* resolve: input value can be:
@@ -204,13 +204,13 @@ export function resolvablesBuilder(state: StateObject): Resolvable[] {
/**
* @internalapi A internal global service
*
- * StateBuilder is a factory for the internal [[State]] objects.
+ * StateBuilder is a factory for the internal [[StateObject]] objects.
*
* When you register a state with the [[StateRegistry]], you register a plain old javascript object which
* conforms to the [[StateDeclaration]] interface. This factory takes that object and builds the corresponding
- * [[State]] object, which has an API and is used internally.
+ * [[StateObject]] object, which has an API and is used internally.
*
- * Custom properties or API may be added to the internal [[State]] object by registering a decorator function
+ * Custom properties or API may be added to the internal [[StateObject]] object by registering a decorator function
* using the [[builder]] method.
*/
export class StateBuilder {
@@ -250,10 +250,10 @@ export class StateBuilder {
}
/**
- * Registers a [[BuilderFunction]] for a specific [[State]] property (e.g., `parent`, `url`, or `path`).
+ * Registers a [[BuilderFunction]] for a specific [[StateObject]] property (e.g., `parent`, `url`, or `path`).
* More than one BuilderFunction can be registered for a given property.
*
- * The BuilderFunction(s) will be used to define the property on any subsequently built [[State]] objects.
+ * The BuilderFunction(s) will be used to define the property on any subsequently built [[StateObject]] objects.
*
* @param name The name of the State property being registered for.
* @param fn The BuilderFunction which will be used to build the State property
diff --git a/src/state/stateObject.ts b/src/state/stateObject.ts
index 4e1f568c..4619f1dd 100644
--- a/src/state/stateObject.ts
+++ b/src/state/stateObject.ts
@@ -3,7 +3,7 @@
* @module state
*/
/** for typedoc */
-import { StateDeclaration, _ViewDeclaration } from "./interface";
+import { StateDeclaration, _ViewDeclaration, _StateDeclaration } from "./interface";
import { defaults, values, find, inherit } from "../common/common";
import { propEq } from "../common/hof";
import { Param } from "../params/param";
@@ -13,20 +13,20 @@ import { TransitionStateHookFn } from "../transition/interface";
import { TargetState } from "./targetState";
import { Transition } from "../transition/transition";
import { Glob } from "../common/glob";
-import { isObject } from "../common/predicates";
+import { isObject, isFunction } from "../common/predicates";
/**
* Internal representation of a UI-Router state.
*
* Instances of this class are created when a [[StateDeclaration]] is registered with the [[StateRegistry]].
*
- * A registered [[StateDeclaration]] is augmented with a getter ([[StateDeclaration.$$state]]) which returns the corresponding [[State]] object.
+ * A registered [[StateDeclaration]] is augmented with a getter ([[StateDeclaration.$$state]]) which returns the corresponding [[StateObject]] object.
*
* This class prototypally inherits from the corresponding [[StateDeclaration]].
* Each of its own properties (i.e., `hasOwnProperty`) are built using builders from the [[StateBuilder]].
*/
export class StateObject {
- /** The parent [[State]] */
+ /** The parent [[StateObject]] */
public parent: StateObject;
/** The name used to register the state */
@@ -58,15 +58,15 @@ export class StateObject {
public views: { [key: string]: _ViewDeclaration; };
/**
- * The original [[StateDeclaration]] used to build this [[State]].
+ * The original [[StateDeclaration]] used to build this [[StateObject]].
* Note: `this` object also prototypally inherits from the `self` declaration object.
*/
public self: StateDeclaration;
- /** The nearest parent [[State]] which has a URL */
+ /** The nearest parent [[StateObject]] which has a URL */
public navigable: StateObject;
- /** The parent [[State]] objects from this state up to the root */
+ /** The parent [[StateObject]] objects from this state up to the root */
public path: StateObject[];
/**
@@ -117,7 +117,9 @@ export class StateObject {
* @param stateDecl the user-supplied State Declaration
* @returns {StateObject} an internal State object
*/
- static create(stateDecl: StateDeclaration): StateObject {
+ static create(stateDecl: _StateDeclaration): StateObject {
+ stateDecl = StateObject.isStateClass(stateDecl) ? new stateDecl() : stateDecl;
+
let state = inherit(inherit(stateDecl, StateObject.prototype)) as StateObject;
stateDecl.$$state = () => state;
state.self = stateDecl;
@@ -127,7 +129,11 @@ export class StateObject {
return state;
}
- /** Predicate which returns true if the object is an internal [[State]] object */
+ /** Predicate which returns true if the object is an class with @State() decorator */
+ static isStateClass = (stateDecl: _StateDeclaration): stateDecl is ({ new (): StateDeclaration }) =>
+ isFunction(stateDecl) && stateDecl['__uiRouterState'] === true;
+
+ /** Predicate which returns true if the object is an internal [[StateObject]] object */
static isState = (obj: any): obj is StateObject =>
isObject(obj['__stateObjectCache']);
diff --git a/src/state/stateQueueManager.ts b/src/state/stateQueueManager.ts
index 789e9a3f..d28b9745 100644
--- a/src/state/stateQueueManager.ts
+++ b/src/state/stateQueueManager.ts
@@ -1,7 +1,7 @@
/** @module state */ /** for typedoc */
import { inArray } from "../common/common";
import { isString } from "../common/predicates";
-import { StateDeclaration } from "./interface";
+import { StateDeclaration, _StateDeclaration } from "./interface";
import { StateObject } from "./stateObject";
import { StateBuilder } from "./stateBuilder";
import { StateRegistryListener, StateRegistry } from "./stateRegistry";
@@ -30,7 +30,7 @@ export class StateQueueManager implements Disposable {
this.queue = [];
}
- register(stateDecl: StateDeclaration) {
+ register(stateDecl: _StateDeclaration) {
let queue = this.queue;
let state = StateObject.create(stateDecl);
let name = state.name;
diff --git a/src/state/stateRegistry.ts b/src/state/stateRegistry.ts
index e737576a..e52040aa 100644
--- a/src/state/stateRegistry.ts
+++ b/src/state/stateRegistry.ts
@@ -7,7 +7,7 @@ import { StateObject } from "./stateObject";
import { StateMatcher } from "./stateMatcher";
import { StateBuilder } from "./stateBuilder";
import { StateQueueManager } from "./stateQueueManager";
-import { StateDeclaration } from "./interface";
+import { StateDeclaration, _StateDeclaration } from "./interface";
import { BuilderFunction } from "./stateBuilder";
import { StateOrName } from "./interface";
import { removeFrom } from "../common/common";
@@ -107,9 +107,9 @@ export class StateRegistry {
*
* Gets the root of the state tree.
* The root state is implicitly created by UI-Router.
- * Note: this returns the internal [[State]] representation, not a [[StateDeclaration]]
+ * Note: this returns the internal [[StateObject]] representation, not a [[StateDeclaration]]
*
- * @return the root [[State]]
+ * @return the root [[StateObject]]
*/
root() {
return this._root;
@@ -123,11 +123,11 @@ export class StateRegistry {
* Note: a state will be queued if the state's parent isn't yet registered.
*
* @param stateDefinition the definition of the state to register.
- * @returns the internal [[State]] object.
+ * @returns the internal [[StateObject]] object.
* If the state was successfully registered, then the object is fully built (See: [[StateBuilder]]).
* If the state was only queued, then the object is not fully built.
*/
- register(stateDefinition: StateDeclaration): StateObject {
+ register(stateDefinition: _StateDeclaration): StateObject {
return this.stateQueue.register(stateDefinition);
}
diff --git a/src/state/stateService.ts b/src/state/stateService.ts
index f9eaf250..6d48fbca 100644
--- a/src/state/stateService.ts
+++ b/src/state/stateService.ts
@@ -65,7 +65,7 @@ export class StateService {
*/
get current() { return this.router.globals.current; }
/**
- * The current [[State]]
+ * The current [[StateObject]]
*
* This is a passthrough through to [[UIRouterGlobals.$current]]
*/
diff --git a/src/state/targetState.ts b/src/state/targetState.ts
index 75eecf68..197acbd6 100644
--- a/src/state/targetState.ts
+++ b/src/state/targetState.ts
@@ -33,7 +33,7 @@ import { isString } from "../common/predicates";
* 4) the registered state object (the [[StateDeclaration]])
*
* Many UI-Router APIs such as [[StateService.go]] take a [[StateOrName]] argument which can
- * either be a *state object* (a [[StateDeclaration]] or [[State]]) or a *state name* (a string).
+ * either be a *state object* (a [[StateDeclaration]] or [[StateObject]]) or a *state name* (a string).
* The `TargetState` class normalizes those options.
*
* A `TargetState` may be valid (the state being targeted exists in the registry)
diff --git a/src/transition/interface.ts b/src/transition/interface.ts
index b65a221f..effe7576 100644
--- a/src/transition/interface.ts
+++ b/src/transition/interface.ts
@@ -198,7 +198,7 @@ export interface TransitionHookFn {
* As each lifecycle event occurs, the hooks which are registered for the event and that state are called (in priority order).
*
* @param transition the current [[Transition]]
- * @param state the [[State]] that the hook is bound to
+ * @param state the [[StateObject]] that the hook is bound to
* @param injector (for ng1 or ng2 only) the injector service
*
* @returns a [[HookResult]] which may alter the transition
@@ -700,7 +700,7 @@ export interface IHookRegistry {
_registeredHooks: { [key: string]: RegisteredHook[] }
}
-/** A predicate type which takes a [[State]] and returns a boolean */
+/** A predicate type which takes a [[StateObject]] and returns a boolean */
export type IStateMatch = Predicate
/**
* This object is used to configure whether or not a Transition Hook is invoked for a particular transition,
diff --git a/src/url/urlRule.ts b/src/url/urlRule.ts
index 34318233..8ae0a09d 100644
--- a/src/url/urlRule.ts
+++ b/src/url/urlRule.ts
@@ -21,7 +21,7 @@ import {
* - `string`
* - [[UrlMatcher]]
* - `RegExp`
- * - [[State]]
+ * - [[StateObject]]
* @internalapi
*/
export class UrlRuleFactory {
diff --git a/src/view/interface.ts b/src/view/interface.ts
index 1c79f875..92a5c86d 100644
--- a/src/view/interface.ts
+++ b/src/view/interface.ts
@@ -33,7 +33,7 @@ export interface ActiveUIView {
*
* A `ViewConfig` is the runtime definition of a single view.
*
- * During a transition, `ViewConfig`s are created for each [[_ViewDeclaration]] defined on each "entering" [[State]].
+ * During a transition, `ViewConfig`s are created for each [[_ViewDeclaration]] defined on each "entering" [[StateObject]].
* Then, the [[ViewService]] finds any matching `ui-view`(s) in the DOM, and supplies the ui-view
* with the `ViewConfig`. The `ui-view` then loads itself using the information found in the `ViewConfig`.
*