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`. *